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

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

Difference From 00532db4911111ae To 5c2d6c24ac6f1a49

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

Added TODO.md.



























































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

---

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

---

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

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

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

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

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

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

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

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

   => save old github stores before removing from management

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

1. mirror config store - changes - broken

Changes to _scratch_/gh-org-pull.

1
2
3

4
5
6
7
8
9
10
#!/usr/bin/env tclsh
## -*- tcl -*-
# # ## ### ##### ######## ############# ######################


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

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




>







1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env tclsh
## -*- tcl -*-
# # ## ### ##### ######## ############# ######################
## Syntax: $0 user-or-org ?name?

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

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

Changes to _scratch_/git-tag-check.

1
2
3

4
5
6
7
8
9
10
#!/usr/bin/env tclsh
## -*- tcl -*-
# # ## ### ##### ######## ############# ######################


package require Tcl 8.6
package require m::futil

proc main {} {
    global argv




>







1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env tclsh
## -*- tcl -*-
# # ## ### ##### ######## ############# ######################
## Syntax: $0 gitdir

package require Tcl 8.6
package require m::futil

proc main {} {
    global argv

Changes to _scratch_/v3-exec.tcl.

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

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

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








<







27
28
29
30
31
32
33

34
35
36
37
38
39
40

### Failure

Flag for attend.

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


## Magic remote

### Success

Store is updated, nothing more.

Added doc/schema-v2-issues.md.









































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# V2 Schema Issues

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

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

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

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

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

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

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

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

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

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

Added doc/schema-v2.md.































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# V2 - Mirroring. Backing up Sources

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

Streamlined v1 to basics.

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

  - Removed tags. Not needed for an initial setup.

## Entities

### Overview

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

### Examples

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

### Core Relations

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

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

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

```

### Entity Attributes

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

## State keys and semantics

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

Added doc/schema-v3.md.

































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# V3 - Mirroring. Backing up Sources

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

## Changes

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

Redesign of the fork handling:

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

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

     Unreported known forks are deactivated. __Not__ removed.

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

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

## Entities

### Overview

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

### Examples

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

### Core Relations

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

A checking contraint:

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

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

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

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

### Entity Attributes

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

## State keys and semantics

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

Deleted doc/schema.md.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# V2 - Mirroring. Backing up Sources

Streamlined v1 to basics.

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

   - Removed tags. Not needed for an initial setup.

## Entities

   1. Repository.
      Examples:

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

      A versioned collection of files.

   1. Mirror Set.
      Example:

      - First item.

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

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

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

   1. Store

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

   1. Name

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

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

## Entity Relations

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

Repository	belongs-to-a		Mirror Set
		n:1

Mirror Set	has-a			Name
		1:1

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

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

As a diagram

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

```

## Entities & Attributes

### version_control_system

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

### name

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

### mirror_set

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

### mset_pending

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

### repository

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

### rolodex

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

### store

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

### store_times

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

## Entities & Attributes around submission handling

### submission

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

### submission_handled

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

### rejected

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

### reply

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

## Entities & Attributes for Internal Management

### schema

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

### state

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

Known keys

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














































































































































































































































































































































































































































































































Changes to lib/cli/cmdr.tcl.

201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227

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

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

    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]







|
|
|

|
|



|
|
|
|







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.
	} { optional
	    validate [m::cmdr::vt project]
	    generate [m::cmdr::call glue gen_current_project]
	}
    }

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

    common .optional-repository {
	input repository {
	    Repository to operate on.
	} { optional
	    validate [m::cmdr::vt repository]
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
	    input path {
		New location of the store
	    } { optional ; validate rwpath }
	} [m::cmdr::call glue cmd_store]

	private take {
	    description {
		Query/change the number of mirror sets processed per
		update cycle.
	    }
	    input take {
		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.
	    }







|



|







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
		update cycle.
	    }
	    input take {
		New number of projects to process in one update.
	    } { optional ; validate cmdr::validate::posint }
	} [m::cmdr::call glue cmd_take]

	private report {
	    description {
		Query/change the email address to send reports to.
	    }
380
381
382
383
384
385
386
387
388
389
390



391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
	use .optional-repository
    } [m::cmdr::call glue cmd_remove]

    private add {
	use .cms
	description {
	    Add repository. The new repository is placed into its own
	    mirror set. Command tries to auto-detect vcs type if not
	    specified. Command derives a name from the url if not
	    specified. New repository becomes current.
	}



	option vcs {
	    Version control system handling the repository.
	} {
	    validate [m::cmdr::vt vcs]
	    generate [m::cmdr::call glue gen_vcs]
	}
	state vcs-code {
	    Version control system handling the repository.
	    Internal code, derived from the option value (database id).
	} {
	    generate [m::cmdr::call glue gen_vcs_code]
	}
	input url {
	    Location of the repository to add.
	} { validate [m::cmdr::vt url] }
	option name {
	    Name for the mirror set to hold the repository.
	} {
	    alias N
	    validate str
	    generate [m::cmdr::call glue gen_name]
	}
    } [m::cmdr::call glue cmd_add]

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

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

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

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

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

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

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

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

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

    private import {
	use .cms.ex
	description {
	    Read a set of repositories and mirror sets from the
	    specified file, or stdin, and add them here. Ignores known
	    repositories. Makes new mirror sets on name
	    conflicts. Ignores mirror sets with no repositories
	    (including only ignored repositories). Processes the
	    format generated by export.
	}
	option dated {
	    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]







|



>
>
>
















|










|
|



|

|






|
|

|

|

|




|





|
|
|
|


















|







|
|
|
|
|
|


|







380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
	use .optional-repository
    } [m::cmdr::call glue cmd_remove]

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

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

	    The rolodex does not change.
	}
	use .optional-project
	input name {
	    New name for the project.
	} { 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
	    current repository is used as the merge target. When no
	    projects are specified at all the projects of
	    current and previous repositories are merged, using
	    the prooject of current as merge target

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

	    The rolodex does not change.
	}
	use .list-optional-project
    } [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.

	    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
	    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.
	}
	option dated {
	    Add datestamp to the generated projects.
	} { presence }
	input spec {
	    Path to the file to read the import specification from.
	    Falls back to stdin when no file is specified.
	} { optional ; validate rchan ; default stdin }

    } [m::cmdr::call glue cmd_import]
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
	    Swap current and previous repository
	}
    } [m::cmdr::call glue cmd_swap_current]

    private update {
	use .cms
	description {
	    Runs an update cycle on the specified mirror sets. When no
	    mirror sets are specified use the next `take` number of
	    mirror sets from the list of pending mirror sets. If no
	    mirror sets are pending refill the list with the entire
	    set of mirror sets and then take from the list.
	}
	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 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.







|
|
|
|
|

|














|
|
|







513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
	    Swap current and previous repository
	}
    } [m::cmdr::call glue cmd_swap_current]

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

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

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

    private issues {
	use .cms.in
	description {
	    Show list of active stores with issues.
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
	    alias L
	    validate [m::cmdr::vt limit]
	    generate [m::cmdr::call glue gen_limit]
	}
	input pattern {
	    When specified, search for repositories matching the
	    pattern.  This is a case-insensitive substring search on
	    repository urls and mirror set names. A search overrides
	    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 {







|







577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
	    alias L
	    validate [m::cmdr::vt limit]
	    generate [m::cmdr::call glue gen_limit]
	}
	input pattern {
	    When specified, search for repositories matching the
	    pattern.  This is a case-insensitive substring search on
	    repository urls and project names. A search overrides
	    and voids any and all repository and limit specifications.
	    This also keeps the cursor unchanged. The rolodex however
	    is filled with the search results.
	} { optional ; validate str }
    } [m::cmdr::call glue cmd_list]

    private reset {
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
	    state vcs-code {
		Version control system handling the repository.
		Internal code, derived from the option value (database id).
	    } {
		generate [m::cmdr::call glue gen_vcs_code]
	    }
	    option name {
		Name for the future mirror set to hold the submitted repository.
	    } {
		alias N
		validate str
		generate [m::cmdr::call glue gen_name]
	    }
	} [m::cmdr::call glue cmd_submit]








|







642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
	    state vcs-code {
		Version control system handling the repository.
		Internal code, derived from the option value (database id).
	    } {
		generate [m::cmdr::call glue gen_vcs_code]
	    }
	    option name {
		Name for the future project to hold the submitted repository.
	    } {
		alias N
		validate str
		generate [m::cmdr::call glue gen_name]
	    }
	} [m::cmdr::call glue cmd_submit]

665
666
667
668
669
670
671



672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
	    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 nomail {
		Disable generation and sending of acceptance mail.
	    } { presence }
	    state vcs-code {
		Version control system handling the repository.
		Internal code, derived from the option value (database id).
	    } {
		generate [m::cmdr::call glue gen_vcs_code]
	    }
	    state url {
		Location of the repository. Taken from the submission.
	    } { validate str
		generate [m::cmdr::call glue gen_submit_url]
	    }
	    option name {
		Name for the mirror set to hold the repository.
		Overrides the name from the submission.
	    } {
		alias N
		validate str
		generate [m::cmdr::call glue gen_submit_name]
	    }
	} [m::cmdr::call glue cmd_accept]







>
>
>















|







668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
	    option vcs {
		Version control system handling the repository.
		Override the submission.
	    } {
		validate [m::cmdr::vt vcs]
		generate [m::cmdr::call glue gen_submit_vcs]
	}
	    option track-forks {
		Force tracking when seeing a large number of forks.
	    } { presence }
	    option nomail {
		Disable generation and sending of acceptance mail.
	    } { presence }
	    state vcs-code {
		Version control system handling the repository.
		Internal code, derived from the option value (database id).
	    } {
		generate [m::cmdr::call glue gen_vcs_code]
	    }
	    state url {
		Location of the repository. Taken from the submission.
	    } { validate str
		generate [m::cmdr::call glue gen_submit_url]
	    }
	    option name {
		Name for the project to hold the repository.
		Overrides the name from the submission.
	    } {
		alias N
		validate str
		generate [m::cmdr::call glue gen_submit_name]
	    }
	} [m::cmdr::call glue cmd_accept]
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968

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

	private test-vt-mset {
	    description {
		Show the knowledge map used by the mirror-set validator.
	    }
	} [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]








|

|

|







956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974

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

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

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

Changes to lib/cli/glue.tcl.

129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
    debug.m/glue {[debug caller] | [$p config] }
    debug.m/glue {[debug caller] | --> $vcode ($vcs) }
    return $vcs
}

proc ::m::glue::gen_name {p} {
    debug.m/glue {[debug caller] | }
    package require m::mset
    package require m::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] \







|







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

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

proc ::m::glue::gen_current_mset {p} {
    debug.m/glue {[debug caller] | }
    # 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 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::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?]} {







|

|







|
|
|
|













|







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

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

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

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

    set dated [$config @dated]
    if {[$config @spec set?]} {
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
		  [ImportRead $sname [$config @spec]]]]
    SiteRegen
    OK
}

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

    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








|


|







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

    m msg [m project spec]
}

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

359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
	}] show
    }
    OK
}

proc ::m::glue::cmd_show {config} {
    debug.m/glue {[debug caller] | }
    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 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} {







|











|







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::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 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
	if {[$config @take set?]} {
	    m state take [$config @take]
	}

	set n [m state take]
    }

    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







|







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"}]
    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
	}
    }] show
    OK
}

proc ::m::glue::cmd_add {config} {
    debug.m/glue {[debug caller] | }
    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::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
	#    mset	: mirror set id

	#    name	: mirror set name
	#    store      : store id, of backing store for the repo

	m repo remove $repo


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


	# Remove store for the repo's vcs if no repositories for that
	# vcs remain in the mirror set.
	if {![m mset has-vcs $mset $vcs]} {
	    m msg "- Removing $vcode store ..."
	    m store remove $store



	}

	# Remove mirror set if no repositories remain at all.

	if {![m mset size $mset]} {

	    m msg "- Removing mirror set [color note $name] ..."
	    m mset remove $mset



	}

	m rolodex drop $repo
	m rolodex commit
    }

    ShowCurrent $config







|














|






<

|
|
|
|
|
<
>
|
|

|
>
|
<
|

>
|
<
|
|

>
>
>


|
>
|
>
|
|
>
>
>







582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610

611
612
613
614
615
616

617
618
619
620
621
622
623

624
625
626
627

628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
	}
    }] show
    OK
}

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

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

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

    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

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

	m repo remove $repo

	set siblings [m store remotes $store]
	set nsiblings [llength $siblings]

	if {!$nsiblings} {
	    m msg "- Removing unshared $vcode store $store ..."
	    m store remove $store
	} else {
	    set x [expr {($nsiblings == 1) ? "repository" : "repositories"}]
	    m msg "- Keeping $vcode store $store still used by $nsiblings $x"
	}

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

	m rolodex drop $repo
	m rolodex commit
    }

    ShowCurrent $config
654
655
656
657
658
659
660












661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678

679
680

681

682
683
684
685
686
687
688




689
690
691
692
693

694

695
696
697




698
699
700
701
702
703
704
705
706
707
708
709
710
711
712





















713
714
715
716
717
718
719
720
721





722


723

724
725

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



763
764
765
766
767
768
769
770
771
772













































773
774
775
776
777
778
779

780
781
782
783

784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
	    set line [string range $line 0 ${w}-5]...
	}
	lappend r $line
    }
    join $r \n
}













proc ::m::glue::cmd_details {config} {
    debug.m/glue {[debug caller] | }
    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
    # 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]] ..."


	set rinfo [m repo get $repo]
	dict with rinfo {}

	# -> url	: repo url

	#    vcs	: vcs id
	#    vcode	: vcs code
	# *  mset	: mirror set id
	# *  name	: mirror set name
	# *  store      : store id, of backing store for the repo

	# Get pieces ...




	
	lassign [m store remotes $store] remotes plugin
	lappend r Remotes $remotes
	if {[llength $plugin]} {
	    lappend r {*}$plugin

	}


	set path [m store path $store]
	set sd   [m store get $store]




	dict with sd {}
	# -> 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]






















	set status  [SI $stderr]
	set export  [m vcs export $vcs $store]
	set dcommit [DeltaCommitFull $commits $commitp]
	set dsize   [DeltaSizeFull $size $sizep]
	set changed [color note [m format epoch $changed]]
	set updated [m format epoch $updated]
	set created [m format epoch $created]
	





	[table/d t {


	    $t add Status        $status

	    $t add {Mirror Set}  $name
	    $t add VCS           $vcsname

	    $t add {Local Store} $path
	    $t add Size          $dsize
	    $t add Commits       $dcommit
	    if {$export ne {}} {
		$t add Export $export
	    }
	    $t add {Update Stats} $spent
	    $t add {Last Change}  $changed
	    $t add {Last Check}   $updated
	    $t add Created        $created

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

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




		$t add Operation $nllines
		if {$stderr ne {}} {
		    $t add "Notes & Errors" [color bad $nelines]
		} else {
		    $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} {
    if {$stderr eq {}} {

	return [color good OK]
    } else {
	set status images/bad.svg
	return [color bad ATTEND]

    }
}

proc ::m::glue::cmd_enable {flag config} {
    debug.m/glue {[debug caller] | }
    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
	    #    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 `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::mset
    package require m::store

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

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

	Rename $mset $newname
    }

    ShowCurrent $config
    SiteRegen
    OK
}

proc ::m::glue::cmd_merge {config} {
    debug.m/glue {[debug caller] | }
    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 @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 mirror set." \
		NOP
	}

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

	foreach secondary $secondaries {
	    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::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
	#    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 "  Mirror set [color note $name]"

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

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

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

	m repo move/1 $repo $msetnew

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

	    m msg "  Move store ..."

	    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 $msetnew
	}
    }

    ShowCurrent $config
    SiteRegen
    OK
}







>
>
>
>
>
>
>
>
>
>
>
>
|

|






|


|



<

>


>
|
>
|
|
|
|
|
|
<
>
>
>
>
|
<
<
<
<
>
|
>


|
>
>
>
>

|
|
<
|
|
|
|
<
<
<
<



>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>








|
>
>
>
>
>
|
>
>
|
>
|
|
>
|
|
|

|

|
|
|
|

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<

|
|

>
>
>
|

|

|


|
|

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>






|
>
|
|
|
|
>
|




|















|
|
|
|


|
|
|
|
|
<










|



|
|
|







|





|









|






|







|




|


|











|









|
|
|
|
|
|





|

|

|



|
|


|

|

|

|




|






|







661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695

696
697
698
699
700
701
702
703
704
705
706
707
708

709
710
711
712
713




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

727
728
729
730




731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787






















788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894

895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
	    set line [string range $line 0 ${w}-5]...
	}
	lappend r $line
    }
    join $r \n
}

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

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

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

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

    set w [$config @tw]    ;#linenoise columns
    # 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]


	# Basic repository details ...........................
	set rinfo [m repo get $repo]
	dict with rinfo {}
	#m msg "Details of [color note $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

	#    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





	set spent [StatsTime $min_sec $max_sec $win_sec]
	
	# Get store details ...

	set path [m store path $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




	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 {
	    $s borders 0
	    set sibs 0
	    foreach sibling $storesibs {
		if {$sibling == $repo} continue
		incr sibs
		$s add ${sibs}. [Short $sibling]
	    }
	    if {$sibs} { $s add {} {} }
	    $s add Size $dsize
	    $s add Commits       $dcommit
	    if {$export ne {}} {
		$s add Export $export
	    }
	    $s add {Update Stats} $spent
	    $s add {Last Change}  $changed
	    $s add {Last Check}   $updated
	    $s 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
		}
	    } else {
		if {$stdout ne {}} { $s add Operation        [L $stdout] }
		if {$stderr ne {}} { $s add "Notes & Errors" [L $stderr] }
	    }
	}] show return]
	
	[table/d t {
	    $t add {} [color note $url]
	    if {$origin ne {}} {
		$t add Origin $dorigin
	    }
	    $t add Status        "$status $active @[color note [m format epoch $checked]]"
	    $t add Project       $name
	    $t add VCS           $vcsname

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

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

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

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

	}] show
    }
    OK
}

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

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

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

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

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

	    set rinfo [m repo get $repo]
	    dict with rinfo {}
	    # -> url	: repo url
	    #    vcs	: vcs id
	    #    vcode	: vcs code
	    #    project: project id
	    #    name	: project name
	    #    store  : id of backing store for 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.

	}
    }

    ShowCurrent $config
    SiteRegen
    OK
}

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

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

	Rename $project $newname
    }

    ShowCurrent $config
    SiteRegen
    OK
}

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

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

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

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

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

    ShowCurrent $config
    SiteRegen
    OK
}

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

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

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

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

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

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

	m repo move/1 $repo $projectnew

	if {![m project 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
	    # to the new holder to be ok.

	    m msg "  Move store ..."

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

	    m msg "  Split store ..."

	    m store cleave $store $projectnew
	}
    }

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

992
993
994
995
996
997
998

999
1000
1001
1002
1003



1004
1005
1006



1007


1008
1009


1010
1011
1012
1013
1014

1015
1016
1017
1018
1019
1020
1021
1022



1023
1024
1025
1026
1027


1028
1029
1030
1031
1032
1033
1034
1035

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































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



1063
1064
1065



1066
1067
1068
1069
1070
1071

1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084


1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116



1117
1118
1119
1120
1121
1122


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


1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231

    ShowCurrent $config
    OK
}

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


    set startcycle [m state start-of-current-cycle]
    set nowcycle   [clock seconds]
    
    m db transaction {
	set verbose  [$config @verbose]
	set msets    [UpdateSets $startcycle $nowcycle [$config @mirror-sets]]

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

	foreach mset $msets {
	    set mname [m mset name $mset]
	    m msg "Updating Mirror Set [color note $mname] ..."




	    set stores [m mset stores $mset]
	    debug.m/glue {stores = ($stores)}






	    foreach store $stores {
		set vname [m store vcs-name $store]


		if {$verbose} {
		    m msg "  [color note $vname] store ... "
		} else {
		    m msg* "  $vname store ... "
		}


		# TODO MAYBE: List the remotes we are pulling from ?
		# => VCS layer, notification callback ...
		set counts [m store update $store $nowcycle [clock seconds]]
		lassign $counts before after forks remotes spent
		debug.m/glue {update = ($counts)}
		
		set    suffix ""



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



		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


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































	    }
	}
    }

    SiteRegen
    OK
}

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

    m db transaction {



	# 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

	    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]



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

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

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

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



	    if {$take} {
		lappend series [list * $mname $numrepo]
		incr take -1
	    } else {
		lappend series [list {} $mname $numrepo]
	    }


	}
    }

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

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

    m db transaction {
	set series {}
	foreach row [m store issues] {
	    dict with row {}
	    # store mname vcode changed updated created size active remote
	    set size [m format size $size]

	    # urls of repos associated with the store
	    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 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::mset
    package require m::repo
    package require m::rolodex
    package require m::store

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



	    lappend series [list $rid $url $mname $vcode $size]
	    m rolodex push $rid
	}
	m rolodex commit
	set n [llength $series]

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







|


>



|


|
>
|

|
|
<
>
>
>

<
|
>
>
>

>
>
|
<
>
>
|
|
|
<
<
>
|
<
|
|
|
<
|
|
>
>
>
|
<
<
<
|
>
>

|
<
<
|
|
|

>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>













>
>
>



>
>
>




|
|
>





|







>
>
|



|
|













|





|
|
|



|
>
>
>

|


|

>
>




|
|



|










|






|

|


<
|
|
<
<
|
|
|
|















|
|












|






|

|


>
>


















|
|







1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082

1083
1084
1085
1086

1087
1088
1089
1090
1091
1092
1093
1094

1095
1096
1097
1098
1099


1100
1101

1102
1103
1104

1105
1106
1107
1108
1109
1110



1111
1112
1113
1114
1115


1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284

1285
1286


1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360

    ShowCurrent $config
    OK
}

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

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

    m db transaction {
	set verbose  [$config @verbose]
	set repos    [UpdateRepos $startcycle $nowcycle [$config @repositories]]

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

	foreach repo $repos {
	    set ri [m repo get $repo]

	    dict with ri {}
	    # url, active, issues, vcs, vcode, project, name, store
	    # min/max/win_sec, checked, origin


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

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

	    m msg "Updating repository $durl ..."
	    m msg "In project          [color note $name]"
	    if {$verbose} {
		m msg  "  [color note [string totitle $vcode]] store ... "
	    } else {


		m msg* "  [string totitle $vcode] store ... "
	    }

	    set primary [expr {$origin eq {}}]

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


	    set now [clock seconds]
	    lassign [m store update $primary $url $store $nowcycle $now $before] \
		ok duration commits size forks
	    set attend [expr {!$ok || [m store has-issues $store]}]
	    set suffix ", in [color note [m format interval $duration]]"



	    
	    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

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

	    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

	    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]
	}
    }
    lassign [TruncW \
		 {Project VCS Size Commits Time Changed Updated Created} \
		 {1       0   0    0       0    0       0       0} \
		 $series [$config @tw]] \
	titles series
    m msg "Cycles: [m format epoch [m state start-of-previous-cycle]] ... [m format epoch [m state start-of-current-cycle]] ..."
    [table t $titles {
	foreach row $series {
	    $t add {*}$row
	}
    }] show
    OK
}

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

    m db transaction {
	set series {}
	set take   [m state take]

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

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

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

proc ::m::glue::cmd_issues {config} {
    debug.m/glue {[debug caller] | }
    package require m::project
    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
	    dict with row {}
	    # store mname vcode changed updated created size active remote 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} \
		 $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::repo
    package require m::rolodex
    package require m::store

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

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

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






1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292


1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
	    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 {}






		set first [list $name $url]
		debug.m/glue {from request: $first}
		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 (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 {}
	    # 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" : "-"}]



	    set dsize   [DeltaSize $sizekb $sizep]
	    set dcommit [DeltaCommit $commits $commitp]
	    set lastn   [LastTime $lastn]
	    
	    lappend table [list $tag $a $url $name $vcode $dsize $dcommit $lastn]
	    # ................. 0    1   2    3    4      5      6        7
	}
    }

    # See also ShowCurrent
    # TODO: extend list with store times ?
    lassign [TruncW \
		 {Tag {} Repository Set VCS Size Commits Time} \
		 {0 0 1 2 0 -1 -1 0} \
		 $table [$config @tw]] \
	titles series
    [table t $titles {
	foreach row $series {
	    $t add {*}[C $row 2 note] ;# 2 => 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
	    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
	    } 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))

	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
	    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} \
		 $table [$config @tw]] \
	titles series
    [table t $titles {
	foreach row $series {
	    $t add {*}[C $row 2 note] ;# 2 => url
	}
    }] show
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
	    set when [m format epoch $when]

	    lappend series [list $id $when $url $vcode $desc $email $submitter]
	}
    }
    lassign [TruncW \
		 {{} When Url VCS Description Email Submitter} \
		 {0 0 2 0 3 0 1} \
		 $series [$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
	    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} \
		 $series [$config @tw]] \
	titles series
    [table t $titles {
	foreach row $series {
	    $t add {*}[C $row 2 note] ;# 2 => url
	}
    }] show
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
	set series {}
	foreach {url reason} [m submission rejected] {
	    lappend series [list $url $reason]
	}
    }
    lassign [TruncW \
		 {Url Reason} \
		 {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]







|

















|







1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
	set series {}
	foreach {url reason} [m submission rejected] {
	    lappend series [list $url $reason]
	}
    }
    lassign [TruncW \
		 {Url Reason} \
		 {1   0} \
		 $series [$config @tw]] \
	titles series
    [table t $titles {
	foreach row $series {
	    $t add {*}[C $row 0 note] ;# 0 => url
	}
    }] show
    OK
}

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

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

    m db transaction {
	set url       [Url $config]
	set email     [$config @email]
	set submitter [$config @submitter]
	set vcode     [$config @vcs-code]
	set desc      [$config @name]
	set url       [m vcs url-norm $vcode $url]
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
    }
    SiteRegen
    OK
}

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








|







1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
    }
    SiteRegen
    OK
}

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

1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619

1620




1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
	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    
}

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


    m mailer to [$config @destination] [m mail generator test]




    OK    
}

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

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

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

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

proc ::m::glue::cmd_debug_levels {config} {
    debug.m/glue {[debug caller] | }







|







>
|
>
>
>
>
|










|





|

|

|



|













|













|







1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
	m mailer to [$config @destination] [m mail generator reply $message {}]
    } else {
	m db transaction {
	    set message [ComeAroundMail [$config @tw] [m state start-of-current-cycle] [clock seconds]]
	}
	m msg $message
    }
    OK
}

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]
    } on error {e o} {
	m msg [color bad $e]
	exit
    }
    OK
}

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

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

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

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

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

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

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

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

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

1749
1750
1751
1752
1753
1754
1755

1756
1757
1758
1759
1760
1761
1762
}

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)

    #          | list ('R' vcode url)
}

proc ::m::glue::ImportVerify {commands} {
    debug.m/glue {}
    # commands :: list (command)
    # command  :: list ('M' name)

    #           | list ('R' vcode url)

    m msg "Verifying ..."

    foreach {code name} [m vcs all] {
	dict set vcs $code .
	dict set vcs $name .







|
>






|
>







1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
}

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

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

    m msg "Verifying ..."

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

1784
1785
1786
1787
1788
1789
1790
	set command [string trim $command]

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

	lassign $command cmd a b
	switch -exact -- $cmd {
	    M {

		Ping "  $command"
		# M name --> a = name, b = ((empty string))
		if {[llength $command] != 2} {
		    lappend msg "Line [format $lfmt $lno]: Bad syntax: $command"
		}
	    }
	    R {







|
>







1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
	set command [string trim $command]

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

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

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

		lappend command $repo







|







2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030

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

		lappend command $repo
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
    return
}

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

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

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

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

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

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

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

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

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

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







|



|

|
|









|










|




|
|
|




|

|
|
|














|














|
|
|







2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
    return
}

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

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

    if {[llength $repos] == 2} {
	lassign $repos vcode url
	# The project 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.
	    # 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 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
    # 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.
    #
    # - 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
    #   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.

    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 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]
	set repos [linsert $repos 0 dummy_vcode @$mname]
	dict set r @$mname [list dummy_vcs $mset dummy_store]
	set rename 0
    }

    while on {
	set remainder [lassign $repos v u]
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059

2060

2061


2062
2063
2064
2065
2066

2067
2068
2069
2070
2071

2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105

2106


2107
2108



2109
2110

2111


2112
2113



2114
2115

2116

2117



2118

























2119


















2120

2121


2122
2123
2124




















2125
2126
2127
2128
2129
2130
2131
	    Rename $msetp [MakeName $mname]
	    set rename 0
	}

	if {![llength $unmatched]} break

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

    m rolodex commit
    return
}

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



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



    m msg "  Setting up the $vcode store for [color note $url] ..."
    lassign [m store add $vcs $mset $tmpname $url] store spent forks
    m msg "  [color note Done] in [color note [m format interval $spent]]"
    if {$forks ne {}} {

	m msg "  Github: Currently tracking [color note [llength $forks]] additional forks"
	foreach f $forks {
	    m msg "  - [color note $f]"
	}
    }


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

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


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



    set mset [m mset add $name]




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




    m msg "  Setting up the $vcode store ..."
    lassign [m store add $vcs $mset $name $url] _ spent forks



    
    m rolodex commit

    m msg "  [color note Done] in [color note [m format interval $spent]]"

    if {$forks ne {}} {



	m msg "  Github: Currently tracking [color note [llength $forks]] additional forks"

























	foreach f $forks {


















	    m msg "  - [color note $f]"

	}


    }
    return
}





















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

proc ::m::glue::Inval {x isvalid} {







|
|












|

>

>
|
>
>

|
|
|
|
>
|
|
|
|
|
>

|



















|






|



|

>
|
>
>
|
<
>
>
>

<
>

>
>
|
|
>
>
>
|
|
>
|
>

>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>

>
>



>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261

2262
2263
2264
2265

2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
	    Rename $msetp [MakeName $mname]
	    set rename 0
	}

	if {![llength $unmatched]} break

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

    m rolodex commit
    return
}

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

    m msg "> [string totitle $vcode] repository [color note $url]"
    
    # -----------------------
    # vcs project url

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

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

    return [list $vcs $project $store]
}

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

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

    if {[m repo has $url]} {
	m::cmdr::error \
	    "Repository already present" \
	    HAVE_ALREADY REPOSITORY
    }
    if 0 {if {[m project 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.


    m msg "Actions ..."

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

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

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

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

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

    return
}

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

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

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

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

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

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

    return [list $repo $forks]
}

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

proc ::m::glue::Inval {x isvalid} {
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197





2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
	if {$n} {
	    set id -1
	    set series {}
	    foreach r $rolodex {
		incr id
		set rinfo [m repo get $r]
		dict with rinfo {}
		# -> url	: repo url
		#    vcs	: vcs id
		#    vcode	: vcs code
		#    mset	: mirror set id
		#    name	: mirror set name
		#    store  : store id, of backing store for the repo
		
		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} \
		     $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::MakeName {prefix} {
    debug.m/glue {[debug caller] | }
    if {![m mset has $prefix]} { return $prefix }
    set n 1
    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 {}    
    foreach row [m store updates] {
	dict with row {}
	# store mname vcode changed updated created size active remote
	# sizep commits commitp mins maxs lastn
	if {$created eq "."} continue ;# ignore separations
	if {$changed < $current} continue ;# older cycle

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

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

	lassign [TruncW \
		     {Changed VCS {Mirror Set} Time Size Commits} \
		     {0 0 1 0 0 0} \
		     $series \
		     $width] \
	    titles series
	
	table t $titles {
	    foreach row $series {
		$t add {*}$row
	    }
	}
	lappend mail [$t show return]
	$t destroy
    }
    
    MailFooter mail
    return [string map [list @/n/@ $n] [join $mail \n]]
}

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

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

    m msg "Cycle complete, coming around and starting new ..."
    
    set email [m state report-mail-destination]

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

    package require m::mail::generator
    package require m::mailer
    m msg "- [color good "Mailing report to"] [color note $email]"
    
    set comearound [ComeAroundMail [m state mail-width] $current $newcycle]
    m mailer to $email [m mail generator reply $comearound {}]

    m msg [color good OK]
    return
}

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

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

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

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

    # No repositories specified.
    # 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 {}







|
|
|
|
|
|
|











|


















>
>
>
>
>



|

|












|














|









|
|



|








|







|







|












|







|


|


|

|



|
|

|


|
|







2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
	if {$n} {
	    set id -1
	    set series {}
	    foreach r $rolodex {
		incr id
		set rinfo [m repo get $r]
		dict with rinfo {}
		# -> url    : repo url
		#    vcs    : vcs id
		# -> vcode  : vcs code
		#    project: project id
		# -> name   : project name
		#    store  : id of backing store for repo

		lappend tag @$id
		if {$id == ($n-2)} { lappend tag @p }
		if {$id == ($n-1)} { lappend tag @c }
		lappend series [list $tag $url $name $vcode]
		unset tag
	    }
	}
    }
    if {$n} {
	lassign [TruncW \
		     {{} {} {} {}} \
		     {0  1  3  0} \
		     $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 }
    set n 1
    while {[m project has ${prefix}#$n]} { incr n }
    return "${prefix}#$n"
}

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

    # Get updates and convert into a series for the table. A series we
    # can compress width-wise before formatting.
    set series {}
    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} \
		     $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
    # 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} {
    debug.m/glue {[debug caller] | }

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

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

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

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

proc ::m::glue::Dedup {values} {
    debug.m/glue {[debug caller] | }
    # While keeping the order
    set res {}
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
}

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

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

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

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

    # TODO MAYBE: stuff cascading logic into `mset rename` ?
    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 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 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








|


















|
|













|


|








|








|







2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
}

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

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

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

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

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

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

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

    set vcss [m project used-vcs $origin]

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

2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
	    m store move $ostore $target
	} else {
	    m store merge [m store id $vcs $target] $ostore
	}
    }

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

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

    set u [m state mail-user]







|
|







2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
	    m store move $ostore $target
	} else {
	    m store merge [m store id $vcs $target] $ostore
	}
    }

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

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

    set u [m state mail-user]
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
    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 
##

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)







|
|







2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
    return $series
}

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

proc ::m::glue::TruncW {titles weights series width} {
    # series  :: list (row)
    # row     :: list (0..n-1 str)
    # weights :: list (0..k-1 int)
    # titles  :: list (0..n-1 str) - Hard min (for now: include in full width)
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645



2646



2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
    set n [llength [lindex $series 0]]
    set k [llength $weights]

    debug.m/glue { terminal     : $width }
    debug.m/glue { len(series)  : [llength $series] }
    debug.m/glue { len(row)     : $n }
    debug.m/glue { len(weights) : $k ($weights)}
    
    if {$n < $k} {
	set d [expr {$k - $n}]
	set weights [lreplace $weights end-$d end]
	# TODO: Check arith (off by x ?)
    }
    if {$n > $k} {
	set d [expr {$n - $k}]
	lappend weights {*}[lrepeat $d 0]
    }

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

    debug.m/glue { terminal'    : $width (-[expr {3*$n+1}]) }
    debug.m/glue { weights'     : ($weights)}
    
    # Compute series column widths (max len) for all columns.  If the
    # total width is larger than width we have to shrink by weight.
    # Note: Min column width after shrinking is 6 (because we want to
    # show something for each column).  If shrink by weight goes below
    # this min width bump up to it and remove the needed characters
    # from the weight 0 columns, but not below min width.
    set min 6

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

    debug.m/glue { col.widths  = [W wc] }
    
    # max width over all rows.

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

    debug.m/glue { full        = $fw vs terminal $width }
	
    # Nothing to do if the table fits already
    if {$fw <= $width} { return [list $titles $series] }

    # No fit, start shrinking.
    
    # Sum of weights to apportion
    set tw 0
    foreach w $weights { if {$w <= 0} continue ; incr tw $w }

    # Number of characters over the allowed width.
    set over [expr {$fw - $width}]
    debug.m/glue { over         : $over }
    
    # Shrink columns per weight
    set col 0 ; set removed 0
    foreach w $weights {
	set c $col ; incr col
	if {$w <= 0} continue



	set drop [format %.0f [expr {double($over * $w)/$tw}]]



	incr removed $drop
	incr wc($c) -$drop
    }
    # --assert: removed >= over
    debug.m/glue { removed      : $removed }
    # Rounding may cause removed < over, leaving too much chracters behind.
    # 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 {







|















|









|










|






|




|







|




|
>
>
>

>
>
>





|



|
















|







|







2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
    set n [llength [lindex $series 0]]
    set k [llength $weights]

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

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

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

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

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

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

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

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

    # max width over all rows.

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

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

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

    # No fit, start shrinking.

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

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

    # Shrink columns per weight
    set col 0 ; set removed 0
    foreach w $weights {
	set c $col ; incr col
	if {$w <= 0} {
	    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.
    # Run a constant shaver, on the weighted cols
    set over [expr {$over - $removed}]
    if {$over} { ShaveWeighted wc $weights $over }

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

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

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

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

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

    # At last, truncate the series elements to the chosen column
    # widths. Same for the titles.
    set new {}
    foreach row $series {
	set col 0
	set newrow {}
	foreach el $row {
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
    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







|







2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
    foreach el $titles {
	if {[string length $el] > $wc($col)} {
	    set el [string range $el 0 $wc($col)-1]
	}
	lappend newtitles $el
	incr col
    }

    return [list $newtitles $new]
}

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


2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
    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]



    append text "$mins ... $maxs"

    set lastn [split [string trim $lastn ,] ,]
    set n     [llength $lastn]

    if {!$n} { return $text }
    
    set maxn [m state store-window-size]
    if {$n > $maxn} {
	set over    [expr {$n - $maxn}]
	set lastn [lreplace $lastn 0 ${over}-1]
    }
    set n       [llength $lastn]
    set total   [expr [join $lastn +]]
    set avg     [m format interval [format %.0f [expr {double($total)/$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} {







>
>


|
|
<

|
<
<
<
|
<



|
|







2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011

3012
3013



3014

3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
    return [m format interval [lindex [split [string trim $lastn ,] ,] end]]
}

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

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

    set lastn [m format win $lastn]
    set n [llength $lastn]

    if {!$n} { return $text }




    set lastn [m format win-trim $lastn [m state store-window-size]]

    set n       [llength $lastn]
    set total   [expr [join $lastn +]]
    set avg     [m format interval [format %.0f [expr {double($total)/$n}]]]

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

proc ::m::glue::DeltaSizeFull {current previous} {
    append text [m format size $current]
    if {$previous != $current} {
	if {$current < $previous} {
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
    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







|















|






3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
    if {$delta < 0} {
	set color bad
    } else {
	# delta > 0
	set color note
	set delta +$delta
    }

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

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

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

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

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

Changes to lib/db/db.tcl.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
## -*- tcl -*-
# # ## ### ##### ######## ############# #####################
## Mirror database - core access and schema

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

package provide m::db 0

# # ## ### ##### ######## ############# #####################
## Requisites






|


|

|







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 location https://core.tcl.tk/akupries/????
# Meta platform tcl
# Meta summary	   Main database access and schema
# Meta description Main database access and schema
# 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

    I+
    C name  TEXT  NOT NULL  UNIQUE
    T name

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

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

    # - -- --- ----- -------- -------------
    ## Repository - A set of versioned files to back up







|







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.

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

    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'

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

    # - -- --- ----- -------- -------------
    ## Mirror Set Pending - List of repositories waiting for an update
    ##                      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.

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

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

proc ::m::db::SETUP-201810092200 {} {







|







|









|
|
|
|



|










|

|
|
|






|







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

    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'

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

    # - -- --- ----- -------- -------------
    ## Mirror Set Pending - List of repositories waiting for an update
    ##			    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.

    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
    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
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.
    #
    # - Invariant: created <= changed
    #   Because a change can happen only after we have store
    #
    # - (created == changed)
    #   -> 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
    < store_times  store updated updated changed
    #                    ^ 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

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

    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 isdefault INTEGER 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
    >+ '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 submitter TEXT
    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 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)
    > '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

    return
}

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

    D m::db
    # - -- --- ----- -------- -------------
    T^ state
    #                           -- 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 active INTEGER  NOT NULL
    < repository  id url vcs mset '1'
    X vcs mset

    return
}








|


|


|






|
|
|
|

|













|
|

|

|

|
|

|

|
|
|
|












|
|

|






|















|
|

|


















|
|
















|
|

|
|
|
|











|













|
|
|







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.
    #
    # - Invariant: created <= changed
    #	Because a change can happen only after we have store
    #
    # - (created == changed)
    #	-> 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
    < store_times  store updated updated changed
    #			 ^ 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

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

    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 isdefault INTEGER 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
    >+ '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 submitter TEXT
    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 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)
    > '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

    return
}

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

    D m::db
    # - -- --- ----- -------- -------------
    T^ state
    #				-- 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 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
    # 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 description TEXT
    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 description TEXT
    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
    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
    < 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 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
    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
    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








|
|

|
|
|


















|
|
|

|
|
|

















|
|














|
|
|
|
|

















|
|
|
|
|






|




|
|
|
|
|
|



















|
|

















|







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








|







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

    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
	,    name N
	WHERE M.name = N.id
    }












































































































































    return
}

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







|
|




















|




>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>





588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
proc ::m::db::SETUP-201910031116 {} {
    debug.m/db {}
    # Extended mail configuration, width to use for tables.

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

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

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







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>





247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
    C code  TEXT  NOT NULL  UNIQUE ; # Short semi-internal tag
    C name  TEXT  NOT NULL  UNIQUE ; # Human readable name
    T vcs

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

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

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

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

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

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

    X name
    X remotes

    return
}

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

Deleted lib/logic/mset.tcl.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
## -*- tcl -*-
# # ## ### ##### ######## ############# ######################

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

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

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

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

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

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

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

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

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

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

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

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

    set map {}
    set mid {}

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

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

    return $map
}

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

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

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

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

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

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

    set mset [m db last_insert_rowid]

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

    return $mset
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return $taken
}

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

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

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










































































































































































































































































































































































































































































































































































































































































































Added lib/logic/project.tcl.





































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
## -*- tcl -*-
# # ## ### ##### ######## ############# ######################

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

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

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

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

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

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

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

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

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

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

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

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

    set map {}
    set mid {}

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

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

    return $map
}

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

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

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

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

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

    return [m db last_insert_rowid]
}

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

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

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

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

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

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

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

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

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

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

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

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

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

Changes to lib/logic/repo.tcl.

20
21
22
23
24
25
26

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


40
41
42
43
44
45
46
#         ( sizep, commits, commitp, mins, maxs, lastn )

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

package require Tcl 8.5
package require m::state
package require m::rolodex

package require debug
package require debug::caller

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

namespace eval ::m {
    namespace export repo
    namespace ensemble create
}
namespace eval ::m::repo {
    namespace export \
	add remove enable move/mset move/1 has get name \
	known get-n mset search id count


    namespace ensemble create
}

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

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







>











|
|
>
>







20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#         ( sizep, commits, commitp, mins, maxs, lastn )

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

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

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

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

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

debug level  m/repo
debug prefix m/repo {[debug caller] | }
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
	SELECT id
	,      url
	FROM   repository
    } {
	dict set map [string tolower $url] $id
    }

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







|
|







62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
	SELECT id
	,      url
	FROM   repository
    } {
	dict set map [string tolower $url] $id
    }

    # See also m::project::known
    # Note, different ids! repository, not project
    set c {}
    set p {}
    set id -1
    foreach r [m rolodex get] {
	set p $c ; set c $r ; incr id
	dict set map "@$id" $r
    }
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
    }
    
    return $map
}

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

proc ::m::repo::has {url} {
    debug.m/repo {}
    return [m db onecolumn {
	SELECT count (*)







|

|

|

|







85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
    }
    
    return $map
}

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

proc ::m::repo::has {url} {
    debug.m/repo {}
    return [m db onecolumn {
	SELECT count (*)
118
119
120
121
122
123
124
















































125
126
127



128
129
130
131































132
133
134
135
136









137


















138
139
140











141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161

162
163
164
165
166





167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188

189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217

218
219
220
221
222
223
224
    debug.m/repo {}
    return [m db onecolumn {
	SELECT count (*)
	FROM   repository
    }]
}

















































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




    m db eval {
	INSERT
	INTO   repository
	VALUES ( NULL, :url, :vcs, :mset, 1 )































    }

    return [m db last_insert_rowid]
}










proc ::m::repo::mset {repo} {


















    debug.m/repo {}
    set mset [m db onecolumn {
	SELECT mset











	FROM   repository
	WHERE  id = :repo
    }]
    debug.m/repo {=> ($mset)}
    return $mset
}

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

    # Given a repository (by id) follow all the links in the database
    # to retrieve everything related to it
    # - repository (url)
    # - mirror set (id, and name)
    # - vcs        (id, and code)
    # - store      (id)
    # - active
    
    set details [m db eval {
	SELECT 'url'    , R.url
	,      'active' , R.active

	,      'vcs'    , R.vcs
	,      'vcode'  , V.code
	,      'mset'   , R.mset
	,      'name'   , M.name
	,      'store'  , S.id





	FROM   repository             R
	,      mirror_set             M
	,      version_control_system V
	,      store                  S
	WHERE  R.id   = :repo
	AND    M.id   = R.mset
	AND    V.id   = R.vcs
	AND    S.vcs  = R.vcs
	AND    S.mset = R.mset
    }]
    debug.m/repo {=> ($details)}
    return $details
}


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

    set sub [string tolower $substring]
    set series {}
    m db eval {
	SELECT M.name             AS name

	,      R.url              AS url
	,      R.id               AS rid
	,      V.code             AS vcode
	,      S.size_kb          AS sizekb
	,      R.active           AS active
	,      T.min_seconds      AS mins
	,      T.max_seconds      AS maxs
	,      T.window_seconds   AS lastn
	,      S.size_previous    AS sizep
	,      S.commits_current  AS commits
	,      S.commits_previous AS commitp
	FROM   repository             R
	,      mirror_set             M
	,      version_control_system V
	,      store                  S
	,      store_times            T
	WHERE  M.id   = R.mset
	AND    V.id   = R.vcs
	AND    S.mset = R.mset
	AND    S.vcs  = R.vcs
	AND    S.id   = T.store
	ORDER BY M.name ASC
	,        R.url  ASC
    } {
	if {
	    ([string first $sub [string tolower $name]] < 0) &&
	    ([string first $sub [string tolower $url ]] < 0)
	} continue
	lappend series [dict create \

		name    $name \
		url     $url \
		id      $rid \
		vcode   $vcode \
	        sizekb  $sizekb \
		active  $active \
		sizep   $sizep \







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|


>
>
>
|
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>





>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

|
|
>
>
>
>
>
>
>
>
>
>
>



<
<








|






|
>


|
|

>
>
>
>
>

|


|
|
|
|
<












|
>




|
|
|
|




|


<
|

<
<
|
|







>







121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266


267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301

302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330

331
332


333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
    debug.m/repo {}
    return [m db onecolumn {
	SELECT count (*)
	FROM   repository
    }]
}

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

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

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

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

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

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

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

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

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

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

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

    return [m db last_insert_rowid]
}

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

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

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

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

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


}

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)
    # - vcs        (id, and code)
    # - store      (id)
    # - active
    
    set details [m db eval {
	SELECT 'url'    , R.url
	,      'active' , R.is_active
	,      'issues' , R.has_issues
	,      'vcs'    , R.vcs
	,      'vcode'  , V.code
	,      'project', R.project
	,      'name'   , P.name
	,      'store'  , S.id
	,      'min_sec', min_duration
	,      'max_sec', max_duration
	,      'win_sec', window_duration
	,      'checked', checked
	,      'origin' , fork_origin
	FROM   repository             R
	,      project                P
	,      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

    }]
    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
	,      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
	,      S.size_previous    AS sizep
	,      S.commits_current  AS commits
	,      S.commits_previous AS commitp
	FROM   repository             R
	,      project                P
	,      version_control_system V
	,      store                  S

	WHERE  P.id   = R.project
	AND    V.id   = R.vcs


	AND    S.id   = R.store
	ORDER BY P.name ASC
	,        R.url  ASC
    } {
	if {
	    ([string first $sub [string tolower $name]] < 0) &&
	    ([string first $sub [string tolower $url ]] < 0)
	} continue
	lappend series [dict create \
		primary [expr {$origin eq {}}] \
		name    $name \
		url     $url \
		id      $rid \
		vcode   $vcode \
	        sizekb  $sizekb \
		active  $active \
		sizep   $sizep \
246
247
248
249
250
251
252
253

254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283

284
285
286
287
288
289
290
	debug.m/repo {first = ($first)}
    }
    lassign $first mname uname

    set lim [expr {$n + 1}]
    set replist {}
    m db eval {
	SELECT M.name             AS name

	,      R.url              AS url
	,      R.id               AS rid
	,      V.code             AS vcode
	,      S.size_kb          AS sizekb
	,      R.active           AS active
	,      T.min_seconds      AS mins
	,      T.max_seconds      AS maxs
	,      T.window_seconds   AS lastn
	,      S.size_previous    AS sizep
	,      S.commits_current  AS commits
	,      S.commits_previous AS commitp
	FROM   repository             R
	,      mirror_set             M
	,      version_control_system V
	,      store                  S
	,      store_times            T
	WHERE  M.id   = R.mset
	AND    V.id   = R.vcs
	AND    S.mset = R.mset
	AND    S.vcs  = R.vcs
	AND    S.id   = T.store
	-- cursor start clause ...
	AND ((M.name > :mname) OR
	     ((M.name = :mname) AND
	      (R.url >= :uname)))
	ORDER BY M.name ASC
	,        R.url  ASC
	LIMIT :lim
    } {
	lappend replist [dict create \

		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
	debug.m/repo {first = ($first)}
    }
    lassign $first mname uname

    set lim [expr {$n + 1}]
    set replist {}
    m db eval {
	SELECT P.name             AS name
	,      R.fork_origin      AS origin
	,      R.url              AS url
	,      R.id               AS rid
	,      V.code             AS vcode
	,      S.size_kb          AS sizekb
	,      R.is_active        AS active
	,      R.min_duration     AS mins
	,      R.max_duration     AS maxs
	,      R.window_duration  AS lastn
	,      S.size_previous    AS sizep
	,      S.commits_current  AS commits
	,      S.commits_previous AS commitp
	FROM   repository             R
	,      project                P
	,      version_control_system V
	,      store                  S

	WHERE  P.id   = R.project
	AND    V.id   = R.vcs


	AND    S.id   = R.store
	-- cursor start clause ...
	AND ((P.name > :mname) OR
	     ((P.name = :mname) AND
	      (R.url >= :uname)))
	ORDER BY P.name ASC
	,        R.url  ASC
	LIMIT :lim
    } {
	lappend replist [dict create \
		primary [expr {$origin eq {}}] \
		name    $name \
		url     $url \
		id      $rid \
		vcode   $vcode \
		sizekb  $sizekb \
		active  $active \
		sizep   $sizep \
319
320
321
322
323
324
325




326
327
328
329
330
331
332
333







































334































335

336
337
338
339
340




341


342
343



344
345


346












347
348
349



350






351

352
















353

354

355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375




376
377
378
379

proc ::m::repo::remove {repo} {
    debug.m/repo {}
    return [m db eval {
	DELETE
	FROM  repository
	WHERE id = :repo




    }]
}

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







































	WHERE  id = :repo































    }]

}

proc ::m::repo::move/mset {msetold msetnew} {
    debug.m/repo {}
    m db eval {




	UPDATE repository


	SET    mset = :msetnew
	WHERE  mset = :msetold



    }
    return


}













proc ::m::repo::move/1 {repo msetnew} {
    debug.m/repo {}



    m db eval {






	UPDATE repository

	SET    mset = :msetnew
















	WHERE  id   = :repo

    }

    return
}

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

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

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





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







>
>
>
>







|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

>


|

|
>
>
>
>
|
>
>
|
|
>
>
>
|
|
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
>
>
>
|
>
>
>
>
>
>
|
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>

>
|







|


|


|
|
|




>
>
>
>




443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return $taken
}

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

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

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

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

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

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

Changes to lib/logic/store.tcl.

30
31
32
33
34
35
36
37
38
39

40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85

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

    namespace ensemble create
}

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

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

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

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

    set store [Add $vcs $mset]

    set started [clock seconds]
    set counts  [m vcs setup $store $vcs $name $url]
    set spent   [expr {[clock seconds] - $started}]

    lassign $counts _ after forks

    Spent   $store $spent
    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







|

|
>










|

|
|
<
<
|
<
>
|

<
|
|
<
<
|
<
|







<
<
<
<







30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54


55

56
57
58

59
60


61

62
63
64
65
66
67
68
69




70
71
72
73
74
75
76

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

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

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

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

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


    set state [m vcs setup $store $vcs $name $url]

    dict with state {}
    # commits, size, forks, duration


    Size    $store $size
    Commits $store $commits




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

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

    m db eval {




	DELETE
	FROM  store
	WHERE id = :store
    }

    m vcs cleanup $store $vcs
    return
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118


119
120
121
122
123
124
125

126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
    set new  [Add $vcs $msetnew ]
    set name [MSName $msetnew]
    m vcs cleave $vcs $store $new $name
    Size $new
    return
}

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

    set vcs [VCS $store]

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

    set remotes [Remotes $store 1]

    set started [clock seconds]
    set counts  [m vcs update $store $vcs $remotes]


    set spent   [expr {[clock seconds] - $started}]

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

    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]







|
|
|
<

<
<
|
|

|
|
>
>
|

<
|

<
<
>
|
|
<
<
<
|
<
<
<
<
<
<
|
<
<
<
<
<
|
<
|







91
92
93
94
95
96
97
98
99
100

101


102
103
104
105
106
107
108
109
110

111
112


113
114
115



116






117





118

119
120
121
122
123
124
125
126
    set new  [Add $vcs $msetnew ]
    set name [MSName $msetnew]
    m vcs cleave $vcs $store $new $name
    Size $new
    return
}

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




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

    set vcs   [VCS $store]
    set state [m vcs update $store $vcs $url $primary]
    dict with state {}
    # ok, commits, size, forks, duration
    if {!$primary} { set forks {} }


    debug.m/store {update = ($state)}



    if {!$ok} {
	Size    $store $size
	Commits $store $commits



	Times   $store $cycle $now [expr {$commits != $before}]






    }







    return [list $ok $duration $commits $size $forks]
}

proc ::m::store::move {store msetnew} {
    debug.m/store {}
    # copy of `m mset name` - outline? check for dependency circles
    set newname [MSName $msetnew]
    set vcs     [VCS $store]
194
195
196
197
198
199
200




































201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233


234
235
236
237
238

239

240
241






242
243
244
245
246
247
248
}

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





































proc ::m::store::get {store} {
    debug.m/store {}
    m db eval {
	SELECT 'size'    , S.size_kb
	,      'mset'    , S.mset
	,      'vcs'     , S.vcs
	,      'sizep'   , S.size_previous
	,      'commits' , S.commits_current
	,      'commitp' , S.commits_previous
	,      'vcsname' , V.name
	,      'updated' , T.updated
	,      'changed' , T.changed
	,      'created' , T.created
	,      'attend'  , T.attend
	,      'min_sec' , T.min_seconds
	,      'max_sec' , T.max_seconds
	,      'win_sec' , T.window_seconds
	,      'remote'  , (SELECT count (*)
		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   T.store = S.id
	AND   V.id    = S.vcs
    }


}

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

    lappend r [Remotes            $store] ;# Database

    lappend r [m vcs remotes $vcs $store] ;# Plugin supplied
    return $r






}

proc ::m::store::vcs-name {store} {
    debug.m/store {}
    return [m db onecolumn {
	SELECT V.name
	FROM   store                  S







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


|

<





|
|
|
|
|
|
|
|
<
<
<
|
<
<
<
<

<


|
<
|
>
>




|
>
|
>
|
|
>
>
>
>
>
>







167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213

214
215
216
217
218
219
220
221
222
223
224
225
226



227




228

229
230
231

232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
}

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

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

    lappend map @@ [join $repos ,]
    set series {}
    m db eval [string map $map {
	SELECT S.id          AS store
	,      P.name        AS mname
	,      V.code        AS vcode
	,      S.changed     AS changed
	,      S.updated     AS updated
	,      S.created     AS created
	,      R.has_issues  AS attend
	,      S.size_kb     AS size
	,      1             AS remote
	,      R.is_active   AS active
	,      R.fork_origin AS origin
	,      R.url         AS url
	,      R.id          AS rid
	FROM repository             R
	,    store                  S
	,    project                P
	,    version_control_system V
	WHERE R.store   = S.id
	AND   R.project = P.id
	AND   R.vcs     = V.id
	AND   R.id      IN (@@)
	ORDER BY mname ASC
	,        vcode ASC
	,        size  ASC
    }] {
	Srow series ;# upvar column variables
    }
    return $series
}
    
proc ::m::store::get {store} {
    debug.m/store {}
    set details [m db eval {
	SELECT 'size'    , S.size_kb

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




	FROM   store                  S

	,      version_control_system V
	WHERE S.id    = :store
	AND   S.vcs   = V.id

    }]
    debug.m/store {=> ($details)}
    return $details
}

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

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

proc ::m::store::vcs-name {store} {
    debug.m/store {}
    return [m db onecolumn {
	SELECT V.name
	FROM   store                  S
265
266
267
268
269
270
271

272
273
274


275
276
277
278
279
280
281
282
283
284
285
286
287
288

289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316


317
318
319
320
321
322
323
324
325
326
327
328
329
330

331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357


358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373

374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395


396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440


441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478


479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516


517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550

551

552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596

597

598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631

632
633
634
635






























636
637
638
639
640
641
642
643
644
645


646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661


662
663
664
665
666
667
668
proc ::m::store::count {} {
    debug.m/store {}
    return [m db onecolumn {
	SELECT count (*)
	FROM store
    }]
}

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



    set sub [string tolower $substring]
    set series {}
    m db eval {
	SELECT S.id      AS store
	,      M.name    AS mname
	,      V.code    AS vcode
	,      T.changed AS changed
	,      T.updated AS updated
	,      T.created AS created
	,      T.attend  AS attend
	,      S.size_kb AS size
	,      (SELECT count (*)
		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
	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 {} {
    debug.m/store {}



    set series {}
    set last {}
    m db eval {
	SELECT S.id      AS store
	,      M.name    AS mname
	,      V.code    AS vcode
	,      T.changed AS changed
	,      T.updated AS updated
	,      T.created AS created
	,      T.attend  AS attend
	,      S.size_kb AS size
	,      (SELECT count (*)
		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
	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 series ;# upvar column variables
    }
    return $series
}

proc ::m::store::disabled {} {
    debug.m/store {}



    set series {}
    set last {}
    m db eval {
	SELECT S.id      AS store
	,      M.name    AS mname
	,      V.code    AS vcode
	,      T.changed AS changed
	,      T.updated AS updated
	,      T.created AS created
	,      T.attend  AS attend
	,      S.size_kb AS size
	,      1         AS remote
	,      0         AS active
	,      R.id      AS rid
	,      R.url     AS url
	FROM store_times            T

	,    store                  S
	,    mirror_set             M
	,    version_control_system V
	,    repository             R
	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 {} {
    debug.m/store {}



    set series {}
    set last {}
    m db eval {
	SELECT S.id      AS store
	,      M.name    AS mname
	,      V.code    AS vcode
	,      T.changed AS changed
	,      T.updated AS updated
	,      T.created AS created
	,      T.attend  AS attend
	,      S.size_kb AS size
	,      (SELECT count (*)
		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
	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 {} {
    debug.m/store {}



    set series {}
    m db eval {
	SELECT S.id      AS store
	,      M.name    AS mname
	,      V.code    AS vcode
	,      T.changed AS changed
	,      T.updated AS updated
	,      T.created AS created
	,      T.attend  AS attend
	,      S.size_kb AS size
	,      (SELECT count (*)
		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
	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 {} {
    debug.m/store {}



    set series {}
    m db eval {
	SELECT S.id      AS store
	,      M.name    AS mname
	,      V.code    AS vcode
	,      T.changed AS changed
	,      T.updated AS updated
	,      T.created AS created
	,      T.attend  AS attend
	,      S.size_kb AS size
	,      (SELECT count (*)
		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
	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 {} {
    debug.m/store {}



    # From the db.tcl notes on store_times
    #
    # 1. created <= changed <= updated
    # 2. (created == changed) -> never changed.

    set series {}

    # Block 1: Changed stores, changed order descending
    # Insert separators when `updated` changes.
    set last {}
    m db eval {
	SELECT S.id      AS store
	,      M.name    AS mname
	,      V.code    AS vcode
	,      T.changed AS changed
	,      T.updated AS updated
	,      T.created AS created
	,      T.attend  AS attend
	,      S.size_kb AS size
	,      (SELECT 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

	FROM store_times            T

	,    store                  S
	,    mirror_set             M
	,    version_control_system V
	WHERE T.store   = S.id
	AND   S.mset    = M.id
	AND   S.vcs     = V.id
	AND   T.created != T.changed
	ORDER BY T.changed DESC
    } {
	if {($last ne {}) && ($last != $updated)} {
	    Sep series
	}
	Srow+delta series
	set last $updated
    }

    debug.m/store {f/[llength $series]}
    set first [llength $series]

    # Block 2: All unchanged stores, creation order descending,
    # i.e. last created top/first.
    m db eval {
	SELECT S.id      AS store
	,      M.name    AS mname
	,      V.code    AS vcode
	,      T.changed AS changed
	,      T.updated AS updated
	,      T.created AS created
	,      T.attend  AS attend
	,      S.size_kb AS size
	,      (SELECT 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

	FROM store_times            T

	,    store                  S
	,    mirror_set             M
	,    version_control_system V
	WHERE T.store   = S.id
	AND   S.mset    = M.id
	AND   S.vcs     = V.id
	AND   T.created = T.changed
	ORDER BY T.created DESC
    } {
	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} {
    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


    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]


    lappend series $row
    return
}

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

    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 \







>



>
>



|
|
|
|
|
|
|
|
|
<
|
>
|
|
|
<
<
<
<

|

<
<
<












|


>
>



|
|
|
|
|
|
|
|
|
<
|
>
|
|
|
<
<
<
<

|

|
|
|
|
|




|




|


>
>



|
|
|
|
|
|
|
|
|
|
|
|
|
>

|

<
|
|
<
<
|
|









|


>
>



|
|
|
|
|
|
|
|
|
<
|
|
|
|
<
<
<
<

|

|
|
|















|


>
>


|
|
|
|
|
|
|
|
|
<
|
|
|
|
<
<
<
<

|

|
|
|









|


>
>


|
|
|
|
|
|
|
|
|
<
|
|
|
|
<
<
<
<

|

|
|
|









|


>
>
|










|
|
|
|
|
|
|
|
<
<
<
|
<
<
<
<
|
|
|
|



>
|
>

|

|
|
|
|
|














|
|
|
|
|
|
|
|
<
<
<
|
<
<
<
<
|
|
|
|



>
|
>

|

|
|
|
|
|




















|




|
>

|


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>









|
>
>




|






|




>
>







274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298

299
300
301
302
303




304
305
306



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335

336
337
338
339
340




341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382

383
384


385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412

413
414
415
416




417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453

454
455
456
457




458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488

489
490
491
492




493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531



532




533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572



573




574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
proc ::m::store::count {} {
    debug.m/store {}
    return [m db onecolumn {
	SELECT count (*)
	FROM store
    }]
}

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

    # List stores ...
    
    set sub [string tolower $substring]
    set series {}
    m db eval {
	SELECT S.id          AS store
	,      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



	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
    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
	,      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.has_issues  = 1    -- Flag for "has issues"
	AND   R.is_active   > 0    -- Flag for "not completely disabled"
	AND   R.project = P.id
	AND   R.vcs     = V.id
	ORDER BY mname ASC
	,        vcode ASC
	,        size  ASC
    } {
	Srow+origin series ;# upvar column variables
    }
    return $series
}

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

    # List repositories ...
    
    set series {}
    set last {}
    m db eval {
	SELECT S.id          AS store
	,      P.name        AS mname
	,      V.code        AS vcode
	,      S.changed     AS changed
	,      S.updated     AS updated
	,      S.created     AS created
	,      R.has_issues  AS attend
	,      S.size_kb     AS size
	,      1             AS remote
	,      0             AS active
	,      R.id          AS rid
	,      R.url         AS url
	,      R.fork_origin AS origin
	FROM repository             R
	,    store                  S
	,    project                P
	,    version_control_system V

	WHERE R.store     = S.id
	AND   R.is_active = 0    -- Flag for disabled


	AND   R.project   = P.id
	AND   R.vcs       = V.id
	ORDER BY mname ASC
	,        vcode ASC
	,        size  ASC
    } {
	Srow+rid+url series ;# upvar column variables
    }
    return $series
}

proc ::m::store::by-name {} {	;# XXX REWORK move to repo package
    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




	,    store                  S
	,    project                P
	,    version_control_system V
	WHERE R.store   = S.id
	AND   R.project = P.id
	AND   R.vcs     = V.id
	ORDER BY mname ASC
	,        vcode ASC
	,        size  ASC
    } {
	if {($last ne {}) && ($last ne $mname)} {
	    Sep series
	}
	set saved $mname
	set mname [expr {($last eq $mname) ? "" : "$mname"}]
	Srow series ;# upvar column variables
	set last $saved
    }
    return $series
}

proc ::m::store::by-vcs {} {	;# XXX REWORK move to repo package
    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




	,    store                  S
	,    project                P
	,    version_control_system V
	WHERE R.store   = S.id
	AND   R.project = P.id
	AND   R.vcs     = V.id
	ORDER BY vcode ASC
	,        mname ASC
	,        size  ASC
    } {
	Srow series
    }
    return $series
}

proc ::m::store::by-size {} {	;# XXX REWORK move to repo package
    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




	,    store                  S
	,    project                P
	,    version_control_system V
	WHERE R.store   = S.id
	AND   R.project = P.id
	AND   R.vcs     = V.id
	ORDER BY size  DESC
	,        mname ASC
	,        vcode ASC
    } {
	Srow series
    }
    return $series
}

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

    # List repositories ...

    # 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
	,      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 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   S.created != S.changed
	ORDER BY S.changed DESC
    } {
	if {($last ne {}) && ($last != $updated)} {
	    Sep series
	}
	Srow+delta series
	set last $updated
    }

    debug.m/store {f/[llength $series]}
    set first [llength $series]

    # Block 2: All unchanged stores, creation order descending,
    # i.e. last created top/first.
    m db eval {
	SELECT S.id               AS store
	,      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
	,      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 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   S.created = S.changed
	ORDER BY S.created DESC
    } {
	if {$first} { Sep series }
	set changed {}
	set updated {}
	Srow+delta series
	set first 0
    }

    debug.m/store {r/[llength $series]}
    return $series
}

proc ::m::store::move-location {newpath} {
    debug.m/store {}
    m vcs move $newpath
    return
}

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

proc ::m::store::Srow {sv} {	;# 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			    

    debug.m/store {s=$store, m=$mname, v=$vcode, ch=$changed, up=$updated, cr=$created, sz=$size, r=$remote/$active, trouble=$attend, oring=4origin, url=$url}
    
    set row [dict create \
		url     $url \
		origin  $origin \
		store   $store \
		mname   $mname \
		vcode   $vcode \
		changed $changed \
		updated $updated \
		created $created \
		size    $size \
		remote  $remote \
		active  $active \
		attend  $attend \
		]
    lappend series $row
    return
}

proc ::m::store::Srow+origin {sv} {	;# XXX REWORK move to repo package
    debug.m/store {}
    upvar 1 \
	$sv series store store mname mname vcode vcode \
	changed changed updated updated created created \
	size size active active remote remote attend attend \
	origin origin url url rid rid

    debug.m/store {s=$store, m=$mname, v=$vcode, ch=$changed, up=$updated, cr=$created, sz=$size, r=$remote/$active, trouble=$attend, origin=$origin, url=$url, rid=$rid}
    
    set row [dict create \
		rid     $rid \
		url     $url \
		store   $store \
		mname   $mname \
		vcode   $vcode \
		changed $changed \
		updated $updated \
		created $created \
		size    $size \
		remote  $remote \
		active  $active \
		attend  $attend \
		origin  $origin
		]
    lappend series $row
    return
}

proc ::m::store::Srow+delta {sv} {	;# XXX REWORK move to repo package
    debug.m/store {}
    upvar 1 \
	$sv series store store mname mname vcode vcode \
	changed changed updated updated created created \
	size size active active remote remote attend attend \
	sizep sizep commits commits commitp commitp mins mins \
	maxs maxs lastn lastn origin origin url url

    debug.m/store {s=$store, m=$mname, v=$vcode, ch=$changed, up=$updated, cr=$created, sz=$size, r=$remote/$active, trouble=$attend}
    
    set row [dict create \
		url     $url \
	        origin  $origin \
		store   $store \
		mname   $mname \
		vcode   $vcode \
		changed $changed \
		updated $updated \
		created $created \
		size    $size \
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705

706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746



















747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
		commits $commits \
		commitp $commitp \
		]
    lappend series $row
    return
}

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

    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	]

    lappend series $row
    return
}

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.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.vcs  = S.vcs
	AND   R.mset = S.mset
    }]
}




















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 {







|





|

|













|
>




|



















|
|
<
|







|
|
<



>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|


<







|






<
|
<
<
<
<
<
<
<
<
<
<
<
<







697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753

754
755
756
757
758
759
760
761
762
763

764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788

789
790
791
792
793
794
795
796
797
798
799
800
801
802

803












804
805
806
807
808
809
810
		commits $commits \
		commitp $commitp \
		]
    lappend series $row
    return
}

proc ::m::store::Srow+rid+url {sv} {	;# XXX REWORK move to repo package
    debug.m/store {}
    upvar 1 \
	$sv series store store mname mname vcode vcode \
	changed changed updated updated created created \
	size size active active remote remote attend attend \
	rid rid url url origin origin

    debug.m/store {s=$store, m=$mname, v=$vcode, ch=$changed, up=$updated, cr=$created, sz=$size, r=$remote/$active, trouble=$attend, rid=$rid, url=$url, origin=$origin}
    
    set row [dict create \
		store   $store \
		mname   $mname \
		vcode   $vcode \
		changed $changed \
		updated $updated \
		created $created \
		size    $size \
		remote  $remote \
		active  $active \
		attend  $attend \
		rid     $rid \
		url     $url	\
		origin  $origin ]
    lappend series $row
    return
}

proc ::m::store::Sep {sv} {	;# XXX REWORK move to repo package
    debug.m/store {}
    upvar 1 $sv series
    lappend series {
	store   . mname   . vcode . changed .
	updated . created . size  . active  .
	remote  . attend  . rid   . url     .
	mins    . maxs    . lastn . sizep   .
	commits . commitp .
    }
    return
}

proc ::m::store::Remotes {store {onlyactive 0}} {
    debug.m/store {}
    if {$onlyactive} {
	return [m db eval {
	    SELECT R.url
	    FROM   repository R
	    ,      store      S
	    WHERE S.id    = :store
	    AND   R.store = S.id

	    AND   R.is_active
	}]
    }
    
    return [m db eval {
	SELECT R.url
	FROM   repository R
	,      store      S
	WHERE S.id    = :store
	AND   R.store = S.id

    }]
}

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} {
    debug.m/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::Commits {store new} {
    debug.m/store {}

    set current [m db onecolumn {
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872



873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912








913
914
915
916
917
918
919
920
921
922














923
924
925
926
927
928
929
930





















931
932
933
934
935
936
937
938
939
940
941
942
943
944












945
946
947
948
949
950
951
	SET    commits_previous = commits_current -- Parallel assignment
	,      commits_current  = :new            -- Shift values.
	WHERE  id               = :store
    }
    return
}

proc ::m::store::Spent {store new} {
    debug.m/store {}

    m db eval {
	SELECT min_seconds    AS mins
	,      max_seconds    AS maxs
	,      window_seconds AS window
	FROM   store_times
	WHERE  store = :store
    } {}

    debug.m/store {mins  = $mins}
    debug.m/store {maxs  = $maxs}
    debug.m/store {lastn = ($window)}
    
    if {($mins < 0) || ($new < $mins)} { set mins $new }
    if {                $new > $maxs}  { set maxs $new }

    set window [split [string trim $window ,] ,]

    debug.m/store {lastn'= ($window)}
    
    if {[llength $window]} {
	lappend window $new
	set maxlen [m state store-window-size]
	set len    [llength $window]
	if {$len > $maxlen} {
	    set over   [expr {$len - $maxlen}]
	    set window [lreplace $window 0 ${over}-1]
	}
	set new [join $window ,]
    }
    set window ,${new},

    debug.m/store {lastn.= ($window)}

    m db eval {
	UPDATE store_times
	SET    min_seconds    = :mins
	,      max_seconds    = :maxs
	,      window_seconds = :window
	WHERE  store          = :store
    }

    return
}

proc ::m::store::Attend {store} {
    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::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
	,	 0      -- attend
	,	 -1     -- min_seconds (+Infinity)
	,	 0      -- max_seconds
	,	 ''     -- window_seconds
	)
    }
    return $store
}

proc ::m::store::VCS {store} {
    debug.m/store {}
    return [m db onecolumn {
	SELECT vcs
	FROM   store
	WHERE  id = :store
    }]
}

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









proc ::m::store::InitialCommits {} {
    debug.m/store {}
    m db eval {
	SELECT id
	FROM   store
    } {
	InitialCommit $id
    }
    return
}















proc ::m::store::InitialSizes {} {
    debug.m/store {}
    m db eval {
	SELECT id
	FROM   store
    } {
	Size $id





















    }
    return
}

proc ::m::store::InitialIssues {} {
    debug.m/store {}
    m db eval {
	SELECT id
	FROM   store
    } {
	Attend $id
    }
    return
}













proc ::m::store::InitialForks {} {
    debug.m/store {}
    m db eval {
	SELECT S.id AS store
	FROM   store                  S
	,      version_control_system V







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|

|
<
<
<
<
<
|
<
<
<
<
<





<




>
>
>



|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<











|



|
|



>
>
>
>
>
>
>
>










>
>
>
>
>
>
>
>
>
>
>
>
>
>







|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>














>
>
>
>
>
>
>
>
>
>
>
>







820
821
822
823
824
825
826















































827
828
829





830





831
832
833
834
835

836
837
838
839
840
841
842
843
844
845
846
















847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
	SET    commits_previous = commits_current -- Parallel assignment
	,      commits_current  = :new            -- Shift values.
	WHERE  id               = :store
    }
    return
}
















































proc ::m::store::Add {vcs} {
    debug.m/store {}
    set now [clock seconds]











    m db eval {
	INSERT
	INTO   store
	VALUES ( NULL  -- id
	,	 :vcs  -- vcs

	,	 0     -- size_kb
	,	 0     -- size_previous
	,	 0     -- commits_current
	,	 0     -- commits_previous
	,	 :now  -- created
	,	 :now  -- updated
	,	 :now  -- changed
	)
    }

    return [m db last_insert_rowid]
















}

proc ::m::store::VCS {store} {
    debug.m/store {}
    return [m db onecolumn {
	SELECT vcs
	FROM   store
	WHERE  id = :store
    }]
}

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

##
# # ## ### ##### ######## ############# ######################
## ATTENTION
## These commands are part of the database migration step.
## Their use of old tables and columns is intentional!
## At the point they are called by the migration these are
## the current tables and columns

proc ::m::store::InitialCommits {} {
    debug.m/store {}
    m db eval {
	SELECT id
	FROM   store
    } {
	InitialCommit $id
    }
    return
}

proc ::m::store::InitialCommit {store} {
    debug.m/store {}

    set vcs  [VCS $store]
    set revs [m vcs revs $store $vcs]
    m db eval {
	UPDATE store
	SET    commits_current  = :revs
	,      commits_previous = :revs
	WHERE  id               = :store
    }
    return
}

proc ::m::store::InitialSizes {} {
    debug.m/store {}
    m db eval {
	SELECT id
	FROM   store
    } {
	InitialSize $id
    }
    return
}

proc ::m::store::InitialSize {store} {
    debug.m/store {}

    set new     [m vcs size $store]
    set current [m db onecolumn {
	SELECT size_kb
	FROM   store
	WHERE  id = :store
    }]

    if {$new == $current} return

    m db eval {
	UPDATE store
	SET    size_previous = size_kb -- Parallel assignment
	,      size_kb       = :new    -- Shift values.
	WHERE  id            = :store
    }
    return
}

proc ::m::store::InitialIssues {} {
    debug.m/store {}
    m db eval {
	SELECT id
	FROM   store
    } {
	Attend $id
    }
    return
}

proc ::m::store::Attend {store} {
    debug.m/store {}

    set attend [expr {[lindex [m vcs caps $store] 1] ne {}}]
    m db eval {
	UPDATE store_times
	SET    attend = :attend
	WHERE  store  = :store
    }
    return
}

proc ::m::store::InitialForks {} {
    debug.m/store {}
    m db eval {
	SELECT S.id AS store
	FROM   store                  S
	,      version_control_system V
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
    set forks [llength [lindex [m vcs github remotes [m vcs path $store]] 1]]
    m db eval {
	INSERT
	INTO store_github_forks
	VALUES ( :store
	       , :forks )
    }
    return
}

proc ::m::store::ForksSetNew {store forks} {
    debug.m/store {}
    # assert: vcs == github
    m db eval {
	INSERT
	INTO store_github_forks
	VALUES ( :store
	       , :forks )
    }
    return
}

proc ::m::store::ForksSet {store forks} {
    debug.m/store {}
    # assert: vcs == github
    m db eval {
	UPDATE store_github_forks
	SET    nforks = :forks
	WHERE  store  = :store
    }
    return
}

# # ## ### ##### ######## ############# ######################
package provide m::store 0
return







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






972
973
974
975
976
977
978























979
980
981
982
983
984
    set forks [llength [lindex [m vcs github remotes [m vcs path $store]] 1]]
    m db eval {
	INSERT
	INTO store_github_forks
	VALUES ( :store
	       , :forks )
    }























    return
}

# # ## ### ##### ######## ############# ######################
package provide m::store 0
return

Deleted lib/logic/vt_mirrorset.tcl.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
## -*- tcl -*-
# # ## ### ##### ######## ############# #####################
## Repositories - Validation

# @@ Meta Begin
# Package m::validate::mset 0 
# Meta author   {Andreas Kupries}
# Meta location https://core.tcl.tk/akupries/????
# Meta platform tcl
# Meta summary     mirror set validation
# Meta description mirror set validation
# Meta subject    {mirror set - validation}
# Meta require {Tcl 8.5-}
# @@ Meta End

package provide m::validate::mset 0

# # ## ### ##### ######## ############# #####################
## Requisites

package require Tcl 8.5
package require cmdr::validate::common 1.2
package require try
package require m::mset
package require m::repo
package require m::rolodex
package require m::match
package require debug
package require debug::caller

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

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

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

namespace eval ::m {
    namespace export validate
    namespace ensemble create
}
namespace eval ::m::validate {
    namespace export mset
    namespace ensemble create
}
namespace eval ::m::validate::mset {
    namespace export default validate complete release
    namespace ensemble create

    namespace import ::cmdr::validate::common::fail
    namespace import ::cmdr::validate::common::complete-enum
}
# # ## ### ##### ######## ############# #####################

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

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

proc ::m::validate::mset::release  {p x} { return }
proc ::m::validate::mset::default  {p}   {
    return [m repo mset [m rolodex top]]
}
proc ::m::validate::mset::complete {p x} {
    debug.m/validate/mset {} 10
    return [complete-enum [dict keys [m mset known]] 0 $x]
}
proc ::m::validate::mset::validate {p x} {
    debug.m/validate/mset {}

    set known [m mset known]
    set match [m match substring id $known nocase $x]

    switch -exact -- $match {
	ok        { return $id }
	fail      { fail $p MSET "a mirror set"              $x }
	ambiguous { fail $p MSET "an unambiguous mirror set" $x }
    }
}

# # ## ### ##### ######## ############# #####################
## Ready
return
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<










































































































































































Added lib/logic/vt_project.tcl.











































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
## -*- tcl -*-
# # ## ### ##### ######## ############# #####################
## Repositories - Validation

# @@ Meta Begin
# Package m::validate::project 0 
# Meta author   {Andreas Kupries}
# Meta location https://core.tcl.tk/akupries/????
# Meta platform tcl
# Meta summary     mirror set validation
# Meta description mirror set validation
# Meta subject    {mirror set - validation}
# Meta require {Tcl 8.5-}
# @@ Meta End

package provide m::validate::project 0

# # ## ### ##### ######## ############# #####################
## Requisites

package require Tcl 8.5
package require cmdr::validate::common 1.2
package require try
package require m::project
package require m::repo
package require m::rolodex
package require m::match
package require debug
package require debug::caller

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

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

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

namespace eval ::m {
    namespace export validate
    namespace ensemble create
}
namespace eval ::m::validate {
    namespace export project
    namespace ensemble create
}
namespace eval ::m::validate::project {
    namespace export default validate complete release
    namespace ensemble create

    namespace import ::cmdr::validate::common::fail
    namespace import ::cmdr::validate::common::complete-enum
}
# # ## ### ##### ######## ############# #####################

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

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

proc ::m::validate::project::release  {p x} { return }
proc ::m::validate::project::default  {p}   {
    return [m repo mset [m rolodex top]]
}
proc ::m::validate::project::complete {p x} {
    debug.m/validate/project {} 10
    return [complete-enum [dict keys [m project known]] 0 $x]
}
proc ::m::validate::project::validate {p x} {
    debug.m/validate/project {}

    set known [m project known]
    set match [m match substring id $known nocase $x]

    switch -exact -- $match {
	ok        { return $id }
	fail      { fail $p PROJECT "a project"              $x }
	ambiguous { fail $p PROJECT "an unambiguous project" $x }
    }
}

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

Changes to lib/mail/sender.tcl.

53
54
55
56
57
58
59

60
61
62


63
64
65
66
67
68
69

    set token [mime::initialize -string $corpus]

    m msg* "    To: [color name $receiver] ... "

    try {
	set res [smtp::sendmessage $token -header [list To $receiver] {*}[Config]]

	foreach item $res {
	    m msg "    ERR $item"
	}


    } finally {
    }
    
    mime::finalize $token
    m msg [color good OK]
    return
}







>



>
>







53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

    set token [mime::initialize -string $corpus]

    m msg* "    To: [color name $receiver] ... "

    try {
	set res [smtp::sendmessage $token -header [list To $receiver] {*}[Config]]
	# XXX REVISIT This may exit on issues, instead of throwing an error ?!
	foreach item $res {
	    m msg "    ERR $item"
	}
    } on error {e o} {
	m msg [color bad $e]
    } finally {
    }
    
    mime::finalize $token
    m msg [color good OK]
    return
}

Changes to lib/utils/format.tcl.

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



















44

45
46
47
48
49
50
51
debug level  m/format
debug prefix m/format {[debug caller] | }

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

namespace eval m::format {
    namespace export size epoch epoch/short interval
    namespace ensemble create
}
namespace eval m {
    namespace export format
    namespace ensemble create
}

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




















proc m::format::size {x} {

    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







|









>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

>







27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
debug level  m/format
debug prefix m/format {[debug caller] | }

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

namespace eval m::format {
    namespace export size epoch epoch/short interval win win-trim
    namespace ensemble create
}
namespace eval m {
    namespace export format
    namespace ensemble create
}

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

proc m::format::win {lastn} {
    # CSV to list, remove bubbles (empty elements)
    return [lmap x [split $lastn ,] { if {$x eq {}} continue ; set x }]
}

proc m::format::win-trim {lastn max} {
    set len [llength $lastn]
    # As new entries are added at the end trimming is done from the front.
    # This is a naive trimmer, removing elements one by one.
    # Considered ok because we usually need only remove one element anyway.
    while {$len > $max} {
	set lastn [lrange  $lastn 1 end]
	set len   [llength $lastn]
    }
    return $lastn
}

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

proc m::format::size {x} {
    # x is in [KB].
    debug.m/format {}
                              if {$x < 1024} { return ${x}K }
    set x [expr {$x/1024.}] ; if {$x < 1024} { return [format %.1f $x]M }
    set x [expr {$x/1024.}] ; if {$x < 1024} { return [format %.1f $x]G }
    set x [expr {$x/1024.}] ; if {$x < 1024} { return [format %.1f $x]T }
    set x [expr {$x/1024.}] ; if {$x < 1024} { return [format %.1f $x]P }
    set x [expr {$x/1024.}] ;                  return [format %.1f $x]E

Changes to lib/utils/ops.tcl.

38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
}
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 \
	result ok fail commits fork size \
	ok?
    namespace ensemble create
}

# # ## ### ##### ######## ############# ######################
## API







|







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













    set ops {
	setup       {Store Url}
	cleanup     {Store}
	update      {Store Url Bool}
	mergable?   {Store Store}
	merge       {Store Store}
	split       {Store Store}







>
>
>
>
>
>
>
>
>
>
>
>
>







96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
proc ::m::ops::client::Cmdline {v} {
    debug.m/ops/client {}
    global argv
    if {[llength $argv] < 3} {
	Usage "Not enough arguments"
    }
    set argv [lassign $argv vcs logfile operation]

    # All issues, including syntax errors, bad arguments, etc are
    # reported through the log and stdout. This is in an internal
    # support application the user normally will not invoke directly.
    # Thus the log has to be initialized before anything other checks.
    if {[catch {
	LogTo $logfile
    } msg]} {
	err $msg
	fail
	return 0
    }

    set ops {
	setup       {Store Url}
	cleanup     {Store}
	update      {Store Url Bool}
	mergable?   {Store Store}
	merge       {Store Store}
	split       {Store Store}
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
    set types [dict get $ops $operation]
    if {[llength $argv] != [llength $types]} {
	Usage "Wrong # Args for $operation"
    }
    foreach a $argv t $types {
	if {![$t $a]} { Usage "Expected $t, got '$a'" }
    }
    if {[catch {
	LogTo $logfile
    } msg]} {
	err $msg
	fail
	return 0
    }
    upvar 1 $v cmd
    set cmd [linsert $argv 0 $vcs $operation]
    return 1
}

proc ::m::ops::client::Usage {{note {}}} {
    debug.m/ops/client {}







<
<
<
<
<
<
<







130
131
132
133
134
135
136







137
138
139
140
141
142
143
    set types [dict get $ops $operation]
    if {[llength $argv] != [llength $types]} {
	Usage "Wrong # Args for $operation"
    }
    foreach a $argv t $types {
	if {![$t $a]} { Usage "Expected $t, got '$a'" }
    }







    upvar 1 $v cmd
    set cmd [linsert $argv 0 $vcs $operation]
    return 1
}

proc ::m::ops::client::Usage {{note {}}} {
    debug.m/ops/client {}

Changes to lib/utils/setup.tcl.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
## -*- tcl -*-
# # ## ### ##### ######## ############# #####################
## Database utilities - Setup, migration processing, schema management

# @@ Meta Begin
# Package db::setup 0
# Meta author   {Andreas Kupries}
# Meta location https://core.tcl.tk/akupries/????
# Meta platform tcl
# 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






|


|







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 location https://core.tcl.tk/akupries/????
# Meta platform tcl
# 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
debug prefix db/setup {[debug caller] | }

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

namespace eval db::setup {
    namespace import ::db::track::it ; rename it track
    namespace export D C U T T^ I I+ > >+ X < <= /
}

namespace eval db {
    namespace export setup
    namespace ensemble create
}








|







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

proc db::setup::<= {table select} {
    debug.db/setup {}
    T new_${table}

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








|

|







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
    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
    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
	, version INTEGER NOT NULL
	)
	;
	INSERT OR IGNORE
	INTO   schema
	VALUES ('version', 0)
	;







|







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

# # ## ### ##### ######## ############# #####################
## 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.
    
    set origin [join [lrange [file split $url] end-1 end] /]
    set forks  [lsort -dict [m::vcs::git::Get hub forks --raw $origin]]

    if {[m exec err-last-get]} {
	m ops client fail ; return
    }

    foreach fork $forks {
	# unverified estimate (saved)
	m ops client fork $fork
    }
    return
}

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







>
|










|






217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
}

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

proc ::m::vcs::github::ReportForks {url} {
    debug.m/vcs/github {}
    upvar 1 path path
    # 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
    }
    return
}

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

Changes to lib/vcs/vcs.tcl.

52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
}

namespace eval ::m::vcs {
    namespace export \
	setup cleanup update check cleave merge \
	rename id supported all code name \
	detect url-norm name-from-url version \
	move size caps remotes export path revs
    namespace ensemble create

    namespace import ::cmdr::color

    # Operation state: Id counter, and state per operation.
    variable opsid 0
    variable ops   {}







|







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
    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
#     return [$vcode revs $path]
# }

proc ::m::vcs::setup {store vcs name url} {
    debug.m/vcs {}
    # store id -> Using for path.
    # vcs   id -> Decode to plugin name
    # name     -  mset name
    # url      -  repo url
    set path  [Path $store]
    set vcode [code $vcs]

    # Ensure clean new environment
    file delete -force -- $path
    file mkdir            $path

    m futil write $path/%name $name  ;# Mirror set
    m futil write $path/%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.
    







|








|







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
    # 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/%vcs  $vcode ;# Manager

    # Redirect through an external command. This command is currently
    # always `mirror-vcs VCS LOG setup STORE URL`.

    # Ask plugin to fill the store.
    
135
136
137
138
139
140
141
142
143
144
145
146


147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169

170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186


187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
    # [x] duration

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

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

    dict unset state results
    dict unset state msg


    return $state
}

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 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 urls ...\n"
    set failed 0
    foreach u $urls {
	debug.m/vcs {Verifying $u ...}
	if {[m url ok $u xr]} continue

	m futil append $path/%stderr "  Bad url: $u\n"
	set failed 1
    }
    if {$failed} {
	m futil append $path/%stderr "Unable to reach remotes\n"
	# Fake 'no changes', and error
	return {-1 -1 {}}
    }

    # Ask plugin to update the store.
    # Redirect through an external command. This command is currently
    # always `mirror-vcs VCS LOG setup STORE URL`.
    
    Operation ::m::vcs::OpComplete $vcode update \
	{*}[OpCmd $vcode $path $urls 0]
    set state [OpWait]



    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]







|




>
>



|








|
|
|
|


|
<
<
|
|
>

<
<
<
|
|
|







|


>
>








<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167


168
169
170
171



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194























195
196
197
198
199
200
201
    # [x] duration

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

	# Rethrow as something more distinguished for trapping
	E $msg CHILD
    }

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

proc ::m::vcs::update {store vcs url primary} {
    debug.m/vcs {}
    # store id -> Using for path.
    # vcs   id -> Decode to plugin name
    # urls     -  repo urls to use as sources

    set path  [Path $store]
    set vcode [code $vcs]

    # Validate the url to ensure that it is still present. No need to
    # go for the vcs client when we know that it must fail. That said,
    # we store our failure as a pseudo error log for other parts to
    # pick up on.

    m futil write $path/%stderr ""
    m futil write $path/%stdout "Verifying url ...\n"


    debug.m/vcs {Verifying $url ...}
    set ok [m url ok $url xr]
    if {!$ok} {
	m futil append $path/%stderr "  Bad url: $u\n"



	m futil append $path/%stderr "Unable to reach remote\n"
	# Fake an error state ...
	return {ok 0 commits 0 size 0 forks {} results {} msg {Invalid url} duration 0}
    }

    # Ask plugin to update the store.
    # Redirect through an external command. This command is currently
    # always `mirror-vcs VCS LOG setup STORE URL`.
    
    Operation ::m::vcs::OpComplete $vcode update \
	{*}[OpCmd $vcode $path $url $primary]
    set state [OpWait]

    return $state
    
    dict with state {}
    # [x] ok
    # [x] commits
    # [x] size
    # [x] forks
    # [ ] results
    # [x] msg
    # [x] duration























}

proc ::m::vcs::rename {store name} {
    debug.m/vcs {}
    # store id -> Using for path.
    # name     -  new mset name
    set path [Path $store]
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
    # [ ] results
    # [x] msg
    # [ ] duration

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

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








|







228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
    # [ ] results
    # [x] msg
    # [ ] duration

    if {!$ok} {
	# Do not perform any filesystem changes.
	# Rethrow as something more distinguished for trapping
	E $msg CHILD
    }

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

296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368

proc ::m::vcs::check {vcs storea storeb} {
    debug.m/vcs {}
    set patha [Path $storea]
    set pathb [Path $storeb]
    set vcode [code $vcs]

    upvar 1 $iv issues
    set issues {}

    # Redirect through an external command. This command is currently
    # always `mirror-vcs VCS LOG mergable?`.

    Operation ::m::vcs::OpComplete $vcode mergable? \
	{*}[OpCmd $vcode $patha $pathb]
    set state [OpWait]

    dict with state {}
    # [x] ok
    # [ ] commits
    # [ ] size
    # [ ] forks
    # [x] results
    # [x] msg
    # [ ] duration

    if {!$ok} {
	if {[llength $msg]}     { lappend issues {*}$msg     }
	if {[llength $results]} { lappend issues {*}$results }
	return
    } else {
	set flag [lindex $results 0]
	debug.m/vcs {--> $flag}
	return $flag
    }
}

proc ::m::vcs::merge {vcs target origin} {
    debug.m/vcs {}
    set ptarget [Path $target]
    set porigin [Path $origin]
    set vcode   [code $vcs]

    upvar 1 $iv issues
    set issues {}

    # Redirect through an external command. This command is currently
    # always `mirror-vcs VCS LOG merge`.
    
    Operation ::m::vcs::OpComplete $vcode merge \
	{*}[OpCmd $vcode $ptarget $porigin]
    set state [OpWait]

    dict with state {}
    # [x] ok
    # [ ] commits
    # [ ] size
    # [ ] forks
    # [ ] results
    # [x] msg
    # [ ] duration

    if {!$ok} {
	if {[llength $msg]}     { lappend issues {*}$msg     }
	if {[llength $results]} { lappend issues {*}$results }
	return
    }
    
    # Destroy the merged store
    cleanup $origin $vcs
    return
}








<
<
<



















|













<
<
<



















|







273
274
275
276
277
278
279



280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312



313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339

proc ::m::vcs::check {vcs storea storeb} {
    debug.m/vcs {}
    set patha [Path $storea]
    set pathb [Path $storeb]
    set vcode [code $vcs]




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




    # 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
    }
    
    # Destroy the merged store
    cleanup $origin $vcs
    return
}

377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405

406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462

463
464
465
466
467
468
469
    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 }

    }
    return
}

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

proc ::m::vcs::remotes {vcs store} {
    debug.m/vcs {}
    set path  [Path $store]
    set vcode [code $vcs]

    # Ask plugin for remotes it may have.
    return [$vcode remotes $path]
}

proc ::m::vcs::export {vcs store} {
    debug.m/vcs {}
    set path  [Path $store]
    set vcode [code $vcs]

    # Redirect through an external command. This command is currently
    # always `mirror-vcs VCS LOG export STORE`.

    # Ask plugin for CGI script to access the store.
    
    Operation ::m::vcs::OpComplete $vcode export \
	{*}[OpCmd $vcode $path]
    set state [OpWait]

    dict with state {}
    # [x] ok
    # [ ] commits
    # [ ] size
    # [ ] forks
    # [x] results
    # [ ] msg
    # [ ] duration

    if {!$ok} {
	if {![llength $results]} {
	    lappend results "Failed to retrieve export script for $vcs on $path"
	}
	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 \







<
<
<



















>









<
<
<
<
<
<
<
<
<













|











|

|











>







348
349
350
351
352
353
354



355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383









384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
    file copy   -force -- $porigin $pdst

    # Inlined rename of origin's new copy
    m futil write $pdst/%name $dstname
    
    # Split/create vcs specific special resources, if any ...




    # 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::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"
	}
	E [join $results \n] EXPORT
    } else {
	set script [join $results \n]
	debug.m/vcs {--> $script}
	return $script
    }
}

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

proc ::m::vcs::version {vcode iv} {
    debug.m/vcs {}

    upvar 1 $iv issues
    set issues {}

    # Redirect through an external command. This command is currently
    # always `mirror-vcs VCS LOG version`.
    
    Operation ::m::vcs::OpComplete $vcode version \
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513

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

    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.







|







460
461
462
463
464
465
466
467
468
469
470
471
472
473
474

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

    E "Unable to determine vcs for $url" DETECT
}

proc ::m::vcs::url-norm {vcode url} {
    debug.m/vcs {}
    # Normalize the incoming url
    # I.e. for a number of known sites, force the use of the https
    # they support. Further strip known irrelevant trailers.
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
    }
    
    return [string map $map $url]
}

proc ::m::vcs::name-from-url {vcode url} {
    debug.m/vcs {}
    upvar 1 $iv issues
    set issues {}

    # Redirect through an external command. This command is currently
    # always `mirror-vcs VCS LOG url-to-name`.
    
    Operation ::m::vcs::OpComplete $vcode url-to-name \
	{*}[OpCmd $vcode $url]
    set state [OpWait]

    dict with state {}
    # [x] ok
    # [ ] commits
    # [ ] size
    # [ ] forks
    # [x] results
    # [x] msg
    # [ ] duration

    if {!$ok} {
	if {[llength $msg]}     { lappend issues {*}$msg     }
	if {[llength $results]} { lappend issues {*}$results }
	return
    } else {
	set name [lindex $results 0]
	debug.m/vcs {--> $name}
	return $name
    }
}








<
<




















|







492
493
494
495
496
497
498


499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
    }
    
    return [string map $map $url]
}

proc ::m::vcs::name-from-url {vcode url} {
    debug.m/vcs {}



    # 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
    } else {
	set name [lindex $results 0]
	debug.m/vcs {--> $name}
	return $name
    }
}

621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
	    SELECT id
	    FROM   version_control_system
	    WHERE  name = :x
	}]
    }

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

    return $id
}

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








|







580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
	    SELECT id
	    FROM   version_control_system
	    WHERE  name = :x
	}]
    }

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

    return $id
}

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

655
656
657
658
659
660
661




662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679



680
681
682
683
684
685
686

proc ::m::vcs::Path {dir} {
    debug.m/vcs {}
    set path [file normalize [file join [m state store] $dir]]
    debug.m/vcs {=> $path}
    return $path
}





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



    return $::m::vcs::opsresult
}

proc ::m::vcs::OpCmd {vcs args} {
    debug.m/vcs {}
    # Currently only fallback for builtin systems.
    # TODO: Query system configuration first.







>
>
>
>


















>
>
>







614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652

proc ::m::vcs::Path {dir} {
    debug.m/vcs {}
    set path [file normalize [file join [m state store] $dir]]
    debug.m/vcs {=> $path}
    return $path
}

proc ::m::vcs::E {msg args} {
    return -code error -errorcode [linsert $args 0 MIRROR VCS] $msg
}

# # ## ### ##### ######## ############# #####################
## Background operations. Based on jobs.
#
## Caller side
# - Operation DONE VCS OP ...
# - OpCmd VCS ...
#

proc ::m::vcs::OpComplete {state} {
    debug.m/vcs {}
    variable opsresult $state
    return
}

proc ::m::vcs::OpWait {} {
    debug.m/vcs {}
    vwait ::m::vcs::opsresult

    #array set __ $::m::vcs::opsresult ; parray __

    return $::m::vcs::opsresult
}

proc ::m::vcs::OpCmd {vcs args} {
    debug.m/vcs {}
    # Currently only fallback for builtin systems.
    # TODO: Query system configuration first.

Changes to lib/web/site.tcl.

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package require debug
package require debug::caller
package require m::asset
package require m::db
package require m::exec
package require m::format
package require m::futil
package require m::mset
package require m::site
package require m::state
package require m::store
package require m::vcs

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








|







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::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
	! "= Data dependent content ..."
	Contact
	Export		;# (See `export`)
	Search
	Submit
	Stores












	set bytime   [m store updates]
	set byname   [m store by-name]
	set bysize   [m store by-size]
	set byvcs    [m store by-vcs]
	set issues   [m store issues] ;# excludes disabled
	set disabled [m store disabled]

	dict set stats issues   [llength $issues]
	dict set stats disabled [llength $disabled]
	dict set stats size     [m store total-size]
	dict set stats nrepos   [m repo count]
	dict set stats nmsets   [m mset count]
	dict set stats nstores  [m store count]
	dict set stats ccycle   [m state start-of-current-cycle]
	dict set stats pcycle   [m state start-of-previous-cycle]

	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







>
>
>
>
>
>
>
>
>
>
>







|
|
|
|
|
|
|
|







74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
	! "= Data dependent content ..."
	Contact
	Export		;# (See `export`)
	Search
	Submit
	Stores

	# Statistics page
	#   - cycle information (start, end, duration, last duration)
	#   - number of projects, repos, stores
	#   - min, max, average, median n.commits, size.kb
	# Project list    - # repos, link to details
	# Project details - repo list, store links!

	# constrained lists --
	# -- just primaries, no forks
	# -- per VCS, just managed by such
	
	set bytime   [m store updates]
	set byname   [m store by-name]
	set bysize   [m store by-size]
	set byvcs    [m store by-vcs]
	set issues   [m store issues] ;# excludes disabled
	set disabled [m store disabled]

	dict set stats issues    [llength $issues]
	dict set stats disabled  [llength $disabled]
	dict set stats size      [m store total-size]
	dict set stats nrepos    [m repo count]
	dict set stats nprojects [m project count]
	dict set stats nstores   [m store count]
	dict set stats ccycle    [m state start-of-current-cycle]
	dict set stats pcycle    [m state start-of-previous-cycle]

	List "By Last Change"     index.md          $bytime   $stats
	List "By Name, VCS, Size" index_name.md     $byname   $stats
	List "By Size, Name, VCS" index_size.md     $bysize   $stats
	List "By VCS, Name, Size" index_vcs.md      $byvcs    $stats
	List "Issues by Name"     index_issues.md   $issues   $stats
	List "Disabled by Name"   index_disabled.md $disabled $stats
118
119
120
121
122
123
124
125

126
127
128
129
130
131
132











































































































133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216




217



























218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254

255
256
257
258
259
260
261
262
263
264
265
266
























































267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312


313
314
315
316

317


318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345

346
347
348
349
350
351
352
353
354
355










356
357
358
359
360
361
362
363
364
365
366
    uplevel 1 $script
    unset dst silent
    return
}

proc ::m::web::site::Stores {} {
    debug.m/web/site {}
    foreach {mset mname} [m mset all] {

	foreach store [m mset stores $mset] {
	    Store $mset $mname $store
	}
    }
    return
}












































































































proc ::m::web::site::Store {mset mname store} {
    debug.m/web/site {}

    # Get page pieces ...

    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 "$min_sec ... $max_sec"

    set win_sec [split [string trim $win_sec ,] ,]
    set n       [llength $win_sec]
    if {$n} {
	set maxn [m state store-window-size]
	if {$n > $maxn} {
	    set over    [expr {$n - $maxn}]
	    set win_sec [lreplace $win_sec 0 ${over}-1]
	    set n       [llength $win_sec]
	}
	set total [expr [join $win_sec +]]
	set avg   [m format interval [format %.0f [expr {double($total)/$n}]]]
	append spent " ($avg * $n)"
    }
    
    set simg [StatusRefs $attend $active $remote]

    lassign [m vcs caps $store] stdout stderr
    set logo [T "Operation" $stdout]
    set loge [T "Notes & Errors" $stderr]

    if {$commitp != $commits} {
	set delta [expr {$commits - $commitp}]
	if {$delta > 0} {
	    set delta +$delta
	}
	append commits " ($commitp ($delta))"
    }

    set dsize [m format size $size]
    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 $mname]
    append text |||| \n
    append text |---|---|---| \n





    R $simg   {} "[IH 32 images/logo/[m vcs code $vcs].svg $vcsname] $vcsname"



























    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::List {suffix page series stats} {
    debug.m/web/site {}

    dict with stats {}
    # issues
    # disabled
    # size
    # nrepos
    # 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     {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  "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|$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 mname {}
    set last {}
    foreach row $series {
	dict with row {}

	# store mname vcode changed updated created size active remote attend



	if {$created eq "."} {
	    append text "||||||||" \n
	    continue
	}

	if {$changed ne {}} {
	    if {$changed < $ccycle} {
		append text "||__$ccf Start Of Current Cycle__||||||" \n
		set ccycle -1 ;# Prevent further triggers
	    }
	    if {$changed < $pcycle} {
		append text "||__$pcf Start Of Last Cycle__||||||" \n
		set pcycle -1 ;# Prevent further triggers
	    }
	}

	set img     [StatusRefs $attend $active $remote]
	set size    [m format size $size]
	set changed [m format epoch $changed]
	set updated [m format epoch $updated]
	set created [m format epoch $created]

	set vcode   "[IH 32 images/logo/${vcode}.svg $vcode] $vcode"
       	set vcode   [LB store_${store}.html $vcode]
	
	if {$mname ne {}} {
	    set mname [LB store_${store}.html $mname]

	}
	append text "|$img|$mname|$vcode|$size|$changed|$updated|$created|" \n
	set last $mname
    }
    append text \n\n

    append text [F]
    W pages/$page $text
    return
}











proc ::m::web::site::Export {} {
    debug.m/web/site {}
    W static/spec.txt [m mset spec]
    return
}

proc ::m::web::site::Search {} {
    debug.m/web/site {}
    WX static/search [CGI mirror-search]
    return







|
>
|
|





>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|



|
|
|
|
<
<















<
<
|
<

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




<
<
<
<
<
<
<
|
|
<
<
<
<
<
<
<
<
<
<
<
|
|
<
<
<
<
|
<


|

|

>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>










<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







>












>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


|





|






|
|
|
|
|

|





|









|
|






|
>
>




>

>
>


|




















|


|
|
>
|
|








>
>
>
>
>
>
>
>
>
>



|







129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259


260
261
262
263
264
265
266
267
268
269
270
271
272
273
274


275

276
















277
278
279
280







281
282











283
284




285

286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333




















334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
    uplevel 1 $script
    unset dst silent
    return
}

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

    foreach {project name} [m project all] {
	foreach store [m project stores $project] {
	    Store $project $name $store
	}
    }
    return
}

proc ::m::web::site::RLink {repo {follow 1}} {
    debug.m/web/site {}

    set ri [m repo get $repo]
    dict with ri {}
    # active, issues, url, store

    set active [expr {$active  ? "" : "[I images/off.svg "Offline"]"}]
    set issues [expr {!$issues ? "" : "[I images/bad.svg "Attend"]"}]
    if {!$follow} { set origin {} }
    if {$origin ne {}} {
	set origin " a [ForkLogo] from [OLink $origin]"
    }
    set label $active$issues$url
    
    return [LB $url $label]$origin
}

proc ::m::web::site::OLink {repo} {
    debug.m/web/site {}

    set ri [m repo get $repo]
    dict with ri {}
    # active, issues, url, store

    set active [expr {$active  ? "" : "[I images/off.svg "Offline"]"}]
    set issues [expr {!$issues ? "" : "[I images/bad.svg "Attend"]"}]
    set label  $active$issues$url
    
    return [LB store_${store}.html $label]
}

proc ::m::web::site::StatsTime {min_sec max_sec win_sec} {
    debug.m/web/site {}

    # See also ::m::repo::times, ::m::glue::StatsTime
    
    set min_sec [expr {$min_sec < 0 ? "+Inf" : [m format interval $min_sec]}]
    set max_sec [m format interval $max_sec]
    set spent   "$min_sec ... $max_sec"
    set win_sec [m format win $win_sec]
    set n       [llength $win_sec]
    if {$n} {
	set win_sec [m format win-trim $win_sec [m state store-window-size]]
	set total   [expr [join $win_sec +]]
	set avg     [m format interval [format %.0f [expr {double($total)/$n}]]]
	append spent " \[avg $avg (over $n)]"
    }
    return $spent
}

proc ::m::web::site::Commits {commits commitp} {
    debug.m/web/site {}
    
    if {$commitp != $commits} {
	set delta [expr {$commits - $commitp}]
	if {$delta > 0} {
	    set delta +$delta
	}
	append commits " ($commitp ($delta))"
    }
    return $commits
}

proc ::m::web::site::Size {size sizep} {
    debug.m/web/site {}
    
    set dsize [m format size $size]
    if {$sizep != $size} {
	set dsizep [m format size $sizep]
	if {$size < $sizep} {
	    # shrink
	    set delta -[m format size [expr {$sizep - $size}]]
	} else {
	    # grow
	    set delta +[m format size [expr {$size - $sizep}]]
	}
	append dsize " ($dsizep ($delta))"
    }
    return $dsize
}

proc ::m::web::site::ExportStore {vcs store} {
    debug.m/web/site {}
    
    set export [m vcs export $vcs $store]
    if {$export ne {}} {
	set path external/local_${store}
	WX static/$path $export
	set export [LB $path {Local Site}]
    }

    return $export
}

proc ::m::web::site::StoreForks {pname url store serial forks} {
    debug.m/web/site {}

    set series [m store getx $forks]
    set page   store_${store}_forks_${serial}
    set up     [LB store_${store}.html $url]
    set title  "[llength $forks] [ForkLogo] of $up"

    ListSimple $pname $title $page.md $series
    return ${page}.html
}

proc ::m::web::site::Store {project pname 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 }]



    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 spent [StatsTime $min_sec $max_sec $win_sec]


















    lassign [m vcs caps $store] stdout stderr
    set logo [T "Operation" $stdout]
    set loge [T "Notes & Errors" $stderr]








    set commits [Commits $commits $commitp]
    set dsize   [Size $size $sizep]











    set export  [ExportStore $vcs $store]
    set vcslogo [VCSLogo [m vcs code $vcs] $vcsname]




    

    # Assemble page ...

    append text [H $pname]
    append text |||| \n
    append text |---|---:|---| \n

    if {![llength $urls]} {
	R $vcslogo {} {}
    } else {
	set threshold 5
	
	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]





















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

    # 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
	    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   [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::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]
    return
}

proc ::m::web::site::Search {} {
    debug.m/web/site {}
    WX static/search [CGI mirror-search]
    return
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
}

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

    # Data flows
    # - Main
    #   - mset_pending			local, no sync
    #   - reply				local, no sync
    #   - rolodex			local, no sync
    #   - schema			local, no sync
    #
    #   - 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







|




|
<
|

<







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

617
618

619
620
621
622
623
624
625
}

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

    # Data flows
    # - Main
    #   - repo_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
    #   - store				[1]

    #   - version_control_system	[1], plus copy to vcs
    #
    #   - rejected			push to site rejected, total replacement
    #   - submission			pull from site (insert or update)
    #   - submission_handled		push to site, deletions in submission
    #
    # - Site
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681


682
683









684
685
686
687
688
689
690
    # database. Implemented as `delete all old ; insert all new`.

    m site eval { DELETE FROM store_index }

    # m store search '' (inlined, simply all)
    m db eval {
	SELECT S.id      AS store
	,      N.name    AS mname
	,      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
	,    mirror_set             M
	,    version_control_system V
	,    name                   N
	WHERE T.store   = S.id
	AND   S.mset    = M.id
	AND   S.vcs     = V.id
	AND   M.name    = N.id
    } {
	# 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.vcs  = R.vcs
	    AND   S.mset = R.mset
	}]
	
	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,
		     :mname, :vcode, :page, :remotes, :status,
		     :size, :changed, :updated, :created )
	}
    }

    # Copy the VCS information



    m site eval { DELETE FROM vcs }










    m db eval {
	SELECT id
	,      code
	,      name
	FROM version_control_system
    } {
	m site eval {







|

|
|
|
|

|
<
<
<
|
<
<
<
<
<
|
<

<
<
<
|
<

|








|
<


|









|






>
>
|
|
>
>
>
>
>
>
>
>
>







776
777
778
779
780
781
782
783
784
785
786
787
788
789
790



791





792

793



794

795
796
797
798
799
800
801
802
803
804
805

806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
    # database. Implemented as `delete all old ; insert all new`.

    m site eval { DELETE FROM store_index }

    # m store search '' (inlined, simply all)
    m db eval {
	SELECT S.id      AS store
	,      (SELECT max (P.name) FROM project P, repository R WHERE P.id = R.project AND R.store = S.id) AS pname
	,      V.code    AS vcode
	,      S.changed AS changed
	,      S.updated AS updated
	,      S.created AS created
	,      (SELECT sum (has_issues) FROM repository R WHERE R.store = S.id) AS attend
	,      S.size_kb AS size
	,      (SELECT count (*)         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

	,    version_control_system V



	WHERE S.vcs = V.id

    } {
	# store, pname, vcode, changed, updated, created, size, remote, active, attend

	set page    store_${store}.html
	set status  [StatusIcons $attend $active $remote]
	set remotes [m db eval {
	    SELECT R.url
	    FROM repository R
	    ,    store      S
	    WHERE S.id   = :store
	    AND   S.id   = R.store

	}]
	
	lappend remotes $pname
	set remotes [string tolower [join $remotes { }]]
	# We are using the remotes field for the entire text we can
	# search over.  Should rename the field, not bothering until
	# we need a larger schema change it can be folded into.

	m site eval {
	    INSERT
	    INTO store_index
	    VALUES ( NULL,
		     :pname, :vcode, :page, :remotes, :status,
		     :size, :changed, :updated, :created )
	}
    }

    # Copy the VCS information

    # Logically this ...
    if 0 {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 {
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>











>
>
>
>
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
</svg>
static/images/logo/fossil.svg<svg width="320" height="440" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g transform="translate(-223.69964,-322.98867)" style="fill:#808080;stroke:none"><g transform="matrix(3.4464775e-2,0,0,3.4464775e-2,64.3835,244.04121)" visibility="visible" style="visibility:visible"><path d="M 7207,8311 L 7191,8307 L 7176,8299 L 7162,8289 L 7149,8276 L 7136,8260 L 7124,8242 L 7103,8197 L 7084,8142 L 7069,8078 L 7057,8006 L 7048,7926 L 7041,7744 L 7048,7538 L 7068,7312 L 7104,7073 L 7153,6836 L 7210,6617 L 7274,6421 L 7343,6252 L 7379,6179 L 7415,6115 L 7451,6061 L 7487,6016 L 7522,5981 L 7540,5967 L 7557,5957 L 7574,5949 L 7591,5944 L 7608,5943 L 7624,5944 L 7640,5948 L 7655,5956 L 7669,5966 L 7683,5979 L 7695,5995 L 7707,6013 L 7729,6058 L 7747,6113 L 7762,6177 L 7774,6249 L 7783,6329 L 7790,6511 L 7784,6717 L 7763,6943 L 7727,7182 L 7679,7419 L 7621,7638 L 7557,7834 L 7488,8003 L 7452,8075 L 7416,8139 L 7380,8194 L 7344,8239 L 7309,8274 L 7291,8287 L 7274,8298 L 7257,8306 L 7240,8310 L 7223,8312 L 7207,8311 z"/><path d="M 7607,10301 L 7592,10303 L 7576,10302 L 7560,10299 L 7544,10294 L 7527,10286 L 7511,10275 L 7477,10248 L 7442,10212 L 7408,10169 L 7373,10117 L 7339,10058 L 7272,9921 L 7210,9761 L 7154,9581 L 7106,9386 L 7070,9188 L 7048,9001 L 7040,8829 L 7045,8677 L 7052,8610 L 7063,8549 L 7077,8494 L 7094,8448 L 7114,8409 L 7125,8393 L 7136,8379 L 7149,8368 L 7162,8358 L 7176,8351 L 7191,8347 L 7206,8345 L 7222,8346 L 7238,8349 L 7254,8354 L 7271,8362 L 7288,8372 L 7322,8399 L 7356,8435 L 7391,8479 L 7425,8531 L 7460,8589 L 7526,8726 L 7589,8887 L 7645,9066 L 7693,9262 L 7729,9460 L 7750,9647 L 7758,9818 L 7753,9971 L 7746,10038 L 7735,10099 L 7721,10153 L 7704,10200 L 7684,10239 L 7673,10255 L 7662,10269 L 7649,10280 L 7636,10290 L 7622,10297 L 7607,10301 z"/><path d="M 8749,11736 L 8737,11743 L 8724,11749 L 8709,11752 L 8694,11754 L 8677,11754 L 8659,11751 L 8621,11742 L 8579,11726 L 8534,11703 L 8486,11673 L 8436,11638 L 8330,11551 L 8219,11443 L 8107,11316 L 7995,11173 L 7892,11023 L 7805,10878 L 7735,10739 L 7684,10612 L 7665,10553 L 7652,10499 L 7643,10449 L 7640,10404 L 7643,10365 L 7646,10348 L 7651,10332 L 7657,10317 L 7665,10304 L 7674,10293 L 7685,10284 L 7697,10277 L 7711,10271 L 7725,10268 L 7741,10266 L 7757,10266 L 7775,10268 L 7814,10278 L 7855,10294 L 7900,10317 L 7948,10346 L 7998,10381 L 8104,10469 L 8215,10577 L 8328,10704 L 8440,10847 L 8543,10996 L 8630,11142 L 8699,11280 L 8751,11408 L 8769,11466 L 8783,11521 L 8791,11570 L 8794,11615 L 8791,11655 L 8788,11672 L 8783,11688 L 8777,11703 L 8769,11716 L 8760,11727 L 8749,11736 z"/><path d="M 10683,12127 L 10680,12139 L 10674,12151 L 10666,12162 L 10656,12173 L 10643,12183 L 10628,12192 L 10592,12209 L 10547,12224 L 10494,12237 L 10434,12247 L 10368,12255 L 10217,12263 L 10045,12261 L 9857,12248 L 9657,12224 L 9458,12190 L 9275,12149 L 9110,12103 L 8968,12052 L 8906,12025 L 8852,11999 L 8805,11972 L 8766,11945 L 8736,11918 L 8725,11904 L 8715,11891 L 8708,11878 L 8704,11865 L 8702,11852 L 8702,11840 L 8705,11828 L 8711,11816 L 8719,11805 L 8729,11794 L 8742,11784 L 8757,11775 L 8793,11758 L 8838,11743 L 8891,11730 L 8950,11720 L 9017,11712 L 9168,11704 L 9339,11706 L 9527,11719 L 9727,11743 L 9926,11777 L 10110,11818 L 10275,11864 L 10417,11915 L 10479,11942 L 10533,11968 L 10580,11995 L 10619,12022 L 10649,12049 L 10660,12063 L 10670,12076 L 10677,12089 L 10681,12102 L 10683,12115 L 10683,12127 z"/><path d="M 10761,12053 L 10758,12043 L 10758,12032 L 10760,12021 L 10763,12009 L 10769,11996 L 10777,11983 L 10799,11955 L 10828,11925 L 10864,11894 L 10955,11826 L 11070,11755 L 11206,11682 L 11359,11610 L 11526,11540 L 11696,11478 L 11858,11427 L 12007,11389 L 12140,11363 L 12253,11350 L 12301,11349 L 12343,11351 L 12378,11357 L 12392,11361 L 12405,11367 L 12416,11373 L 12425,11380 L 12432,11388 L 12437,11397 L 12440,11407 L 12440,11418 L 12438,11429 L 12435,11441 L 12429,11454 L 12421,11467 L 12399,11495 L 12370,11525 L 12334,11556 L 12243,11624 L 12127,11695 L 11992,11768 L 11838,11840 L 11671,11910 L 11501,11972 L 11340,12023 L 11191,12061 L 11058,12087 L 10945,12100 L 10897,12101 L 10855,12099 L 10820,12093 L 10806,12089 L 10793,12083 L 10782,12077 L 10773,12070 L 10766,12062 L 10761,12053 z"/><path d="M 12410,11353 L 12408,11351 L 12406,11349 L 12404,11344 L 12402,11337 L 12402,11330 L 12402,11322 L 12403,11312 L 12409,11291 L 12418,11267 L 12430,11239 L 12465,11175 L 12511,11102 L 12568,11022 L 12635,10936 L 12710,10847 L 12788,10761 L 12864,10683 L 12937,10616 L 13003,10560 L 13061,10518 L 13087,10502 L 13110,10490 L 13130,10482 L 13139,10479 L 13147,10478 L 13154,10477 L 13161,10478 L 13166,10480 L 13169,10481 L 13171,10483 L 13173,10485 L 13175,10487 L 13177,10492 L 13179,10499 L 13180,10506 L 13179,10514 L 13178,10524 L 13173,10545 L 13164,10569 L 13152,10597 L 13117,10661 L 13071,10734 L 13014,10814 L 12947,10900 L 12872,10989 L 12794,11075 L 12718,11153 L 12645,11220 L 12579,11276 L 12521,11318 L 12495,11334 L 12472,11346 L 12451,11354 L 12442,11357 L 12434,11358 L 12427,11359 L 12420,11358 L 12415,11356 L 12412,11355 L 12410,11353 z"/><path d="M 8102,11826 L 8102,11791 L 8101,11755 L 8100,11720 L 8098,11685 L 8096,11651 L 8093,11617 L 8089,11583 L 8086,11550 L 8081,11518 L 8077,11487 L 8072,11456 L 8066,11427 L 8060,11398 L 8054,11371 L 8047,11344 L 8039,11319 L 8032,11296 L 8024,11273 L 8016,11252 L 8007,11233 L 7999,11215 L 7990,11198 L 7980,11184 L 7971,11171 L 7961,11159 L 7951,11149 L 7941,11141 L 7931,11135 L 7921,11131 L 7911,11128 L 7901,11127 L 7901,11127 L 7891,11128 L 7881,11131 L 7871,11135 L 7861,11141 L 7851,11149 L 7841,11159 L 7831,11171 L 7822,11184 L 7812,11198 L 7803,11215 L 7795,11233 L 7786,11252 L 7778,11273 L 7770,11296 L 7763,11319 L 7755,11344 L 7748,11371 L 7742,11398 L 7736,11427 L 7730,11456 L 7725,11487 L 7721,11518 L 7716,11550 L 7713,11583 L 7709,11617 L 7706,11651 L 7704,11685 L 7702,11720 L 7701,11755 L 7700,11791 L 7700,11826 L 7700,11826 L 7700,11861 L 7701,11897 L 7702,11932 L 7704,11967 L 7706,12001 L 7709,12035 L 7713,12069 L 7716,12102 L 7721,12134 L 7725,12165 L 7730,12196 L 7736,12225 L 7742,12254 L 7748,12281 L 7755,12308 L 7763,12333 L 7770,12356 L 7778,12379 L 7786,12400 L 7795,12419 L 7803,12437 L 7812,12454 L 7822,12468 L 7831,12481 L 7841,12493 L 7851,12503 L 7861,12511 L 7871,12517 L 7881,12521 L 7891,12524 L 7901,12525 L 7901,12525 L 7911,12524 L 7921,12521 L 7931,12517 L 7941,12511 L 7951,12503 L 7961,12493 L 7971,12481 L 7980,12468 L 7990,12454 L 7999,12437 L 8007,12419 L 8016,12400 L 8024,12379 L 8032,12356 L 8039,12333 L 8047,12308 L 8054,12281 L 8060,12254 L 8066,12225 L 8072,12196 L 8077,12165 L 8081,12134 L 8086,12102 L 8089,12069 L 8093,12035 L 8096,12001 L 8098,11967 L 8100,11932 L 8101,11897 L 8102,11861 L 8102,11826 z"/><path d="M 7825,12576 L 7819,12584 L 7810,12591 L 7801,12597 L 7789,12601 L 7777,12604 L 7762,12606 L 7730,12607 L 7692,12603 L 7649,12595 L 7602,12583 L 7551,12567 L 7438,12522 L 7313,12463 L 7181,12391 L 7043,12306 L 6910,12215 L 6790,12123 L 6685,12033 L 6599,11948 L 6563,11907 L 6533,11869 L 6508,11833 L 6490,11800 L 6477,11770 L 6473,11756 L 6471,11743 L 6470,11731 L 6471,11719 L 6474,11709 L 6479,11700 L 6486,11692 L 6494,11685 L 6504,11679 L 6515,11675 L 6528,11672 L 6542,11670 L 6575,11669 L 6613,11673 L 6656,11681 L 6703,11693 L 6754,11709 L 6867,11753 L 6992,11812 L 7124,11884 L 7262,11969 L 7395,12061 L 7515,12153 L 7619,12243 L 7706,12328 L 7741,12369 L 7771,12407 L 7796,12443 L 7815,12476 L 7827,12506 L 7831,12520 L 7834,12533 L 7834,12545 L 7833,12557 L 7830,12567 L 7825,12576 z"/><path d="M 6460,11695 L 6457,11697 L 6454,11699 L 6451,11701 L 6447,11702 L 6443,11703 L 6438,11703 L 6428,11702 L 6416,11700 L 6403,11696 L 6389,11691 L 6374,11684 L 6342,11666 L 6307,11643 L 6270,11616 L 6233,11584 L 6197,11550 L 6166,11517 L 6139,11485 L 6118,11455 L 6110,11441 L 6103,11427 L 6098,11415 L 6094,11404 L 6092,11393 L 6092,11389 L 6092,11385 L 6093,11381 L 6094,11377 L 6096,11374 L 6098,11371 L 6101,11369 L 6104,11366 L 6107,11365 L 6111,11364 L 6115,11363 L 6120,11363 L 6130,11363 L 6142,11366 L 6154,11370 L 6168,11375 L 6183,11382 L 6216,11399 L 6251,11422 L 6288,11450 L 6325,11481 L 6361,11515 L 6392,11548 L 6418,11581 L 6439,11611 L 6448,11625 L 6455,11638 L 6460,11651 L 6464,11662 L 6465,11672 L 6466,11677 L 6465,11681 L 6465,11685 L 6464,11689 L 6462,11692 L 6460,11695 z"/><path d="M 13184,10437 L 13182,10436 L 13179,10434 L 13175,10430 L 13171,10424 L 13168,10418 L 13166,10410 L 13164,10401 L 13163,10379 L 13164,10353 L 13167,10322 L 13179,10251 L 13200,10167 L 13229,10073 L 13266,9970 L 13309,9862 L 13357,9756 L 13405,9659 L 13453,9572 L 13498,9499 L 13540,9440 L 13560,9416 L 13578,9398 L 13595,9384 L 13602,9378 L 13610,9374 L 13616,9372 L 13623,9370 L 13629,9371 L 13631,9371 L 13634,9372 L 13636,9373 L 13639,9375 L 13643,9379 L 13646,9385 L 13649,9391 L 13651,9399 L 13653,9408 L 13655,9430 L 13654,9456 L 13651,9487 L 13638,9558 L 13617,9642 L 13588,9736 L 13551,9839 L 13508,9947 L 13461,10053 L 13413,10150 L 13365,10237 L 13320,10311 L 13278,10369 L 13258,10393 L 13240,10411 L 13223,10425 L 13216,10431 L 13208,10435 L 13202,10437 L 13195,10439 L 13189,10438 L 13187,10438 L 13184,10437 z"/><path d="M 10098,10825 L 10098,10790 L 10097,10754 L 10096,10719 L 10094,10684 L 10092,10650 L 10089,10616 L 10086,10582 L 10082,10549 L 10078,10517 L 10073,10486 L 10068,10455 L 10062,10426 L 10056,10397 L 10050,10370 L 10043,10343 L 10036,10318 L 10029,10295 L 10021,10272 L 10013,10251 L 10004,10232 L 9996,10214 L 9987,10197 L 9977,10183 L 9968,10170 L 9959,10158 L 9949,10148 L 9939,10140 L 9929,10134 L 9919,10130 L 9909,10127 L 9899,10126 L 9899,10126 L 9889,10127 L 9879,10130 L 9869,10134 L 9859,10140 L 9849,10148 L 9839,10158 L 9830,10170 L 9821,10183 L 9811,10197 L 9802,10214 L 9794,10232 L 9785,10251 L 9777,10272 L 9769,10295 L 9762,10318 L 9755,10343 L 9748,10370 L 9742,10397 L 9736,10426 L 9730,10455 L 9725,10486 L 9720,10517 L 9716,10549 L 9712,10582 L 9709,10616 L 9706,10650 L 9704,10684 L 9702,10719 L 9701,10754 L 9700,10790 L 9700,10825 L 9700,10825 L 9700,10860 L 9701,10896 L 9702,10931 L 9704,10966 L 9706,11000 L 9709,11034 L 9712,11068 L 9716,11101 L 9720,11133 L 9725,11164 L 9730,11195 L 9736,11224 L 9742,11253 L 9748,11280 L 9755,11307 L 9762,11332 L 9769,11355 L 9777,11378 L 9785,11399 L 9794,11418 L 9802,11436 L 9811,11453 L 9821,11467 L 9830,11480 L 9839,11492 L 9849,11502 L 9859,11510 L 9869,11516 L 9879,11520 L 9889,11523 L 9899,11524 L 9899,11524 L 9909,11523 L 9919,11520 L 9929,11516 L 9939,11510 L 9949,11502 L 9959,11492 L 9968,11480 L 9977,11467 L 9987,11453 L 9996,11436 L 10004,11418 L 10013,11399 L 10021,11378 L 10029,11355 L 10036,11332 L 10043,11307 L 10050,11280 L 10056,11253 L 10062,11224 L 10068,11195 L 10073,11164 L 10078,11133 L 10082,11101 L 10086,11068 L 10089,11034 L 10092,11000 L 10094,10966 L 10096,10931 L 10097,10896 L 10098,10860 L 10098,10825 z"/><path d="M 9827,11575 L 9821,11583 L 9812,11590 L 9803,11596 L 9791,11600 L 9779,11603 L 9764,11605 L 9732,11606 L 9694,11602 L 9651,11594 L 9604,11582 L 9553,11566 L 9440,11521 L 9315,11462 L 9183,11390 L 9045,11305 L 8912,11214 L 8792,11122 L 8687,11032 L 8601,10947 L 8565,10906 L 8535,10868 L 8510,10832 L 8492,10799 L 8479,10769 L 8475,10755 L 8473,10742 L 8472,10730 L 8473,10718 L 8476,10708 L 8481,10699 L 8488,10691 L 8496,10684 L 8506,10678 L 8517,10674 L 8530,10671 L 8544,10669 L 8577,10668 L 8615,10672 L 8658,10680 L 8705,10692 L 8756,10708 L 8869,10752 L 8994,10811 L 9126,10883 L 9264,10968 L 9397,11060 L 9517,11152 L 9621,11242 L 9708,11327 L 9743,11368 L 9773,11406 L 9798,11442 L 9817,11475 L 9829,11505 L 9833,11519 L 9836,11532 L 9836,11544 L 9835,11556 L 9832,11566 L 9827,11575 z"/><path d="M 6085,9230 L 6075,9220 L 6067,9209 L 6060,9197 L 6055,9184 L 6050,9169 L 6047,9154 L 6045,9120 L 6048,9083 L 6055,9042 L 6067,8998 L 6083,8952 L 6104,8904 L 6128,8853 L 6190,8749 L 6266,8641 L 6357,8533 L 6456,8432 L 6555,8346 L 6653,8274 L 6701,8245 L 6747,8220 L 6791,8199 L 6833,8183 L 6873,8172 L 6910,8165 L 6944,8164 L 6960,8166 L 6975,8169 L 6989,8173 L 7001,8179 L 7013,8186 L 7024,8194 L 7034,8204 L 7042,8215 L 7049,8227 L 7054,8241 L 7059,8255 L 7062,8271 L 7064,8304 L 7062,8342 L 7054,8383 L 7043,8426 L 7027,8473 L 7006,8521 L 6981,8571 L 6920,8676 L 6843,8784 L 6753,8892 L 6654,8993 L 6554,9079 L 6456,9151 L 6409,9180 L 6362,9205 L 6318,9226 L 6276,9242 L 6236,9253 L 6199,9259 L 6165,9260 L 6149,9259 L 6134,9256 L 6120,9251 L 6108,9246 L 6096,9239 L 6085,9230 z"/><path d="M 5910,9183 L 5900,9185 L 5890,9184 L 5879,9182 L 5868,9177 L 5856,9171 L 5845,9163 L 5820,9141 L 5795,9113 L 5769,9078 L 5743,9037 L 5716,8991 L 5663,8882 L 5611,8754 L 5561,8612 L 5516,8456 L 5479,8299 L 5451,8150 L 5434,8014 L 5427,7893 L 5427,7839 L 5430,7791 L 5435,7748 L 5443,7711 L 5454,7680 L 5460,7667 L 5467,7656 L 5474,7647 L 5483,7639 L 5491,7634 L 5501,7630 L 5511,7628 L 5521,7629 L 5532,7631 L 5543,7636 L 5555,7642 L 5566,7650 L 5591,7671 L 5616,7700 L 5642,7735 L 5668,7776 L 5695,7822 L 5748,7931 L 5800,8058 L 5850,8201 L 5895,8357 L 5932,8514 L 5960,8663 L 5977,8799 L 5984,8920 L 5984,8974 L 5981,9022 L 5975,9065 L 5967,9102 L 5957,9133 L 5951,9146 L 5944,9157 L 5936,9166 L 5928,9174 L 5919,9179 L 5910,9183 z"/><path d="M 8630,9344 L 8623,9336 L 8617,9328 L 8613,9318 L 8609,9306 L 8607,9294 L 8607,9281 L 8609,9251 L 8615,9217 L 8626,9180 L 8642,9140 L 8662,9097 L 8713,9004 L 8779,8903 L 8858,8798 L 8950,8691 L 9048,8589 L 9144,8500 L 9238,8425 L 9325,8365 L 9366,8341 L 9405,8321 L 9440,8306 L 9473,8296 L 9503,8291 L 9516,8291 L 9529,8291 L 9540,8294 L 9550,8297 L 9560,8302 L 9568,8308 L 9575,8316 L 9581,8325 L 9585,8335 L 9588,8346 L 9590,8358 L 9591,8372 L 9589,8401 L 9582,8435 L 9571,8472 L 9556,8512 L 9536,8555 L 9485,8648 L 9419,8749 L 9339,8854 L 9248,8961 L 9150,9063 L 9054,9152 L 8960,9228 L 8873,9288 L 8832,9312 L 8793,9331 L 8758,9346 L 8725,9356 L 8695,9361 L 8682,9362 L 8669,9361 L 8658,9359 L 8648,9355 L 8638,9350 L 8630,9344 z"/><path d="M 8566,9557 L 8557,9563 L 8547,9566 L 8536,9568 L 8524,9569 L 8511,9568 L 8497,9565 L 8465,9555 L 8431,9539 L 8393,9517 L 8353,9490 L 8310,9458 L 8218,9378 L 8120,9282 L 8019,9170 L 7917,9044 L 7821,8913 L 7739,8787 L 7670,8668 L 7617,8558 L 7597,8509 L 7581,8462 L 7569,8420 L 7562,8383 L 7561,8350 L 7562,8336 L 7564,8323 L 7567,8311 L 7572,8301 L 7578,8292 L 7586,8285 L 7595,8279 L 7605,8276 L 7616,8274 L 7628,8273 L 7641,8274 L 7655,8277 L 7687,8287 L 7721,8303 L 7759,8325 L 7799,8352 L 7842,8385 L 7934,8464 L 8032,8561 L 8133,8673 L 8235,8799 L 8330,8930 L 8413,9056 L 8482,9175 L 8535,9285 L 8555,9334 L 8571,9380 L 8583,9422 L 8589,9460 L 8591,9492 L 8590,9507 L 8588,9520 L 8585,9531 L 8580,9541 L 8574,9550 L 8566,9557 z"/><path d="M 6578,11626 L 6575,11627 L 6572,11627 L 6569,11627 L 6565,11626 L 6561,11624 L 6557,11622 L 6549,11616 L 6540,11608 L 6531,11598 L 6521,11586 L 6510,11573 L 6489,11541 L 6467,11503 L 6445,11460 L 6424,11413 L 6405,11365 L 6390,11319 L 6379,11277 L 6371,11239 L 6369,11222 L 6367,11207 L 6367,11193 L 6367,11181 L 6369,11171 L 6370,11166 L 6372,11163 L 6374,11159 L 6376,11157 L 6378,11155 L 6381,11153 L 6384,11152 L 6387,11152 L 6391,11152 L 6394,11153 L 6398,11155 L 6402,11157 L 6411,11163 L 6420,11171 L 6429,11181 L 6439,11193 L 6449,11206 L 6471,11238 L 6493,11276 L 6514,11319 L 6535,11366 L 6554,11414 L 6569,11460 L 6581,11502 L 6588,11540 L 6591,11557 L 6592,11572 L 6593,11586 L 6592,11598 L 6590,11608 L 6589,11613 L 6587,11616 L 6585,11620 L 6583,11622 L 6581,11624 L 6578,11626 z"/><path d="M 5952,11673 L 5953,11670 L 5955,11667 L 5957,11665 L 5960,11663 L 5963,11660 L 5967,11658 L 5977,11655 L 5989,11652 L 6003,11651 L 6037,11649 L 6077,11651 L 6122,11655 L 6172,11663 L 6224,11674 L 6276,11687 L 6323,11701 L 6366,11717 L 6402,11733 L 6432,11748 L 6444,11756 L 6454,11764 L 6461,11771 L 6464,11775 L 6466,11778 L 6468,11781 L 6469,11785 L 6469,11788 L 6469,11791 L 6468,11794 L 6466,11797 L 6464,11799 L 6461,11802 L 6458,11804 L 6454,11806 L 6444,11809 L 6432,11812 L 6418,11814 L 6384,11815 L 6344,11814 L 6299,11809 L 6249,11802 L 6197,11791 L 6145,11778 L 6098,11763 L 6055,11748 L 6019,11732 L 5989,11716 L 5977,11708 L 5967,11701 L 5960,11693 L 5957,11690 L 5955,11686 L 5953,11683 L 5952,11679 L 5952,11676 L 5952,11673 z"/><path d="M 5384,7616 L 5381,7618 L 5378,7620 L 5375,7622 L 5371,7623 L 5367,7624 L 5362,7624 L 5352,7623 L 5340,7621 L 5327,7617 L 5313,7612 L 5298,7605 L 5266,7587 L 5231,7564 L 5194,7537 L 5157,7505 L 5121,7471 L 5090,7438 L 5063,7406 L 5042,7376 L 5034,7362 L 5027,7348 L 5022,7336 L 5018,7325 L 5016,7314 L 5016,7310 L 5016,7306 L 5017,7302 L 5018,7298 L 5020,7295 L 5022,7292 L 5025,7290 L 5028,7287 L 5031,7286 L 5035,7285 L 5039,7284 L 5044,7284 L 5054,7284 L 5066,7287 L 5078,7291 L 5092,7296 L 5107,7303 L 5140,7320 L 5175,7343 L 5212,7371 L 5249,7402 L 5285,7436 L 5316,7469 L 5342,7502 L 5363,7532 L 5372,7546 L 5379,7559 L 5384,7572 L 5388,7583 L 5389,7593 L 5390,7598 L 5389,7602 L 5389,7606 L 5388,7610 L 5386,7613 L 5384,7616 z"/><path d="M 5502,7547 L 5499,7548 L 5496,7548 L 5493,7548 L 5489,7547 L 5485,7545 L 5481,7543 L 5473,7537 L 5464,7529 L 5455,7519 L 5445,7507 L 5434,7494 L 5413,7462 L 5391,7424 L 5369,7381 L 5348,7334 L 5329,7286 L 5314,7240 L 5303,7198 L 5295,7160 L 5293,7143 L 5291,7128 L 5291,7114 L 5291,7102 L 5293,7092 L 5294,7087 L 5296,7084 L 5298,7080 L 5300,7078 L 5302,7076 L 5305,7074 L 5308,7073 L 5311,7073 L 5315,7073 L 5318,7074 L 5322,7076 L 5326,7078 L 5335,7084 L 5344,7092 L 5353,7102 L 5363,7114 L 5373,7127 L 5395,7159 L 5417,7197 L 5438,7240 L 5459,7287 L 5478,7335 L 5493,7381 L 5505,7423 L 5512,7461 L 5515,7478 L 5516,7493 L 5517,7507 L 5516,7519 L 5514,7529 L 5513,7534 L 5511,7537 L 5509,7541 L 5507,7543 L 5505,7545 L 5502,7547 z"/><path d="M 4875,7594 L 4876,7591 L 4878,7588 L 4880,7586 L 4883,7584 L 4886,7581 L 4890,7579 L 4900,7576 L 4912,7573 L 4926,7572 L 4960,7570 L 5000,7572 L 5045,7576 L 5095,7584 L 5147,7594 L 5199,7607 L 5246,7622 L 5289,7637 L 5325,7653 L 5355,7669 L 5367,7677 L 5377,7684 L 5384,7692 L 5387,7695 L 5389,7699 L 5391,7702 L 5392,7706 L 5392,7709 L 5392,7712 L 5391,7715 L 5389,7718 L 5387,7720 L 5384,7722 L 5381,7725 L 5377,7727 L 5367,7730 L 5355,7733 L 5341,7734 L 5307,7736 L 5267,7734 L 5222,7730 L 5172,7722 L 5120,7711 L 5068,7698 L 5021,7684 L 4978,7668 L 4942,7653 L 4912,7637 L 4900,7629 L 4890,7621 L 4883,7614 L 4880,7610 L 4878,7607 L 4876,7604 L 4875,7600 L 4875,7597 L 4875,7594 z"/><path d="M 9763,8248 L 9761,8245 L 9759,8242 L 9758,8238 L 9758,8234 L 9757,8230 L 9758,8226 L 9759,8215 L 9763,8204 L 9768,8192 L 9775,8178 L 9784,8164 L 9805,8134 L 9831,8102 L 9862,8069 L 9897,8035 L 9934,8004 L 9970,7976 L 10005,7953 L 10037,7936 L 10052,7929 L 10066,7923 L 10079,7919 L 10090,7917 L 10100,7916 L 10105,7916 L 10109,7917 L 10113,7918 L 10116,7920 L 10119,7922 L 10122,7924 L 10124,7927 L 10126,7930 L 10127,7933 L 10127,7937 L 10128,7941 L 10127,7946 L 10126,7956 L 10122,7967 L 10117,7979 L 10110,7993 L 10102,8007 L 10080,8037 L 10054,8069 L 10023,8102 L 9988,8136 L 9951,8167 L 9914,8195 L 9880,8218 L 9847,8236 L 9833,8242 L 9819,8248 L 9806,8252 L 9794,8254 L 9784,8255 L 9780,8255 L 9775,8255 L 9772,8254 L 9768,8252 L 9765,8250 L 9763,8248 z"/><path d="M 9639,8179 L 9636,8177 L 9634,8175 L 9632,8173 L 9630,8169 L 9628,8166 L 9627,8161 L 9625,8151 L 9624,8139 L 9625,8126 L 9626,8110 L 9629,8093 L 9636,8056 L 9648,8013 L 9663,7968 L 9682,7920 L 9703,7873 L 9725,7830 L 9747,7792 L 9768,7760 L 9779,7746 L 9789,7734 L 9798,7724 L 9807,7717 L 9815,7711 L 9819,7709 L 9823,7707 L 9827,7706 L 9830,7706 L 9833,7706 L 9836,7707 L 9839,7708 L 9841,7710 L 9843,7713 L 9845,7716 L 9847,7720 L 9848,7724 L 9850,7734 L 9851,7746 L 9850,7760 L 9849,7775 L 9846,7792 L 9839,7830 L 9827,7872 L 9812,7918 L 9793,7966 L 9772,8013 L 9750,8056 L 9728,8094 L 9707,8126 L 9697,8140 L 9687,8152 L 9677,8162 L 9668,8169 L 9660,8175 L 9656,8177 L 9652,8179 L 9648,8180 L 9645,8180 L 9642,8180 L 9639,8179 z"/><path d="M 10269,8228 L 10269,8231 L 10269,8234 L 10268,8237 L 10267,8241 L 10264,8244 L 10262,8248 L 10254,8255 L 10244,8263 L 10232,8270 L 10203,8286 L 10166,8302 L 10123,8317 L 10076,8331 L 10024,8344 L 9972,8355 L 9922,8362 L 9877,8367 L 9837,8368 L 9803,8367 L 9789,8365 L 9777,8362 L 9767,8359 L 9763,8357 L 9760,8355 L 9757,8352 L 9755,8350 L 9753,8347 L 9752,8344 L 9752,8341 L 9752,8338 L 9753,8334 L 9755,8331 L 9757,8327 L 9760,8324 L 9767,8316 L 9777,8309 L 9789,8301 L 9803,8293 L 9819,8285 L 9855,8269 L 9898,8254 L 9946,8240 L 9998,8227 L 10050,8217 L 10099,8209 L 10144,8205 L 10184,8204 L 10218,8205 L 10232,8207 L 10244,8210 L 10254,8213 L 10258,8215 L 10261,8217 L 10264,8220 L 10266,8222 L 10268,8225 L 10269,8228 z"/><path d="M 9749,10085 L 9746,10087 L 9743,10089 L 9740,10091 L 9736,10092 L 9732,10093 L 9727,10093 L 9717,10092 L 9705,10090 L 9693,10086 L 9679,10081 L 9664,10074 L 9631,10056 L 9597,10034 L 9560,10006 L 9523,9975 L 9488,9941 L 9457,9908 L 9430,9876 L 9409,9846 L 9401,9832 L 9394,9819 L 9388,9806 L 9385,9795 L 9383,9785 L 9382,9780 L 9383,9776 L 9383,9772 L 9384,9768 L 9386,9765 L 9388,9762 L 9391,9760 L 9394,9758 L 9397,9756 L 9401,9755 L 9405,9754 L 9410,9754 L 9421,9755 L 9432,9757 L 9445,9761 L 9459,9767 L 9474,9774 L 9507,9791 L 9541,9814 L 9578,9841 L 9615,9872 L 9650,9906 L 9681,9939 L 9707,9971 L 9729,10001 L 9737,10015 L 9744,10029 L 9749,10041 L 9753,10052 L 9754,10063 L 9755,10067 L 9754,10071 L 9754,10075 L 9753,10079 L 9751,10082 L 9749,10085 z"/><path d="M 9868,10017 L 9865,10018 L 9862,10018 L 9859,10018 L 9855,10017 L 9852,10015 L 9848,10013 L 9840,10007 L 9831,9999 L 9821,9989 L 9801,9963 L 9780,9931 L 9758,9893 L 9736,9850 L 9715,9803 L 9696,9755 L 9681,9710 L 9670,9667 L 9662,9630 L 9658,9597 L 9657,9584 L 9658,9572 L 9660,9562 L 9661,9557 L 9662,9554 L 9664,9550 L 9666,9548 L 9668,9546 L 9671,9544 L 9674,9543 L 9677,9543 L 9680,9543 L 9684,9544 L 9688,9546 L 9692,9548 L 9700,9554 L 9709,9562 L 9718,9572 L 9728,9584 L 9739,9598 L 9760,9630 L 9782,9668 L 9803,9711 L 9824,9758 L 9843,9806 L 9858,9851 L 9870,9894 L 9878,9931 L 9880,9948 L 9882,9964 L 9882,9977 L 9882,9989 L 9880,9999 L 9879,10004 L 9877,10007 L 9875,10011 L 9873,10013 L 9871,10015 L 9868,10017 z"/><path d="M 9242,10063 L 9243,10060 L 9245,10057 L 9247,10055 L 9250,10053 L 9253,10050 L 9257,10048 L 9267,10045 L 9279,10042 L 9293,10041 L 9327,10039 L 9367,10041 L 9412,10045 L 9462,10053 L 9514,10064 L 9566,10077 L 9613,10091 L 9656,10107 L 9692,10123 L 9722,10138 L 9734,10146 L 9744,10154 L 9751,10161 L 9754,10165 L 9756,10168 L 9758,10171 L 9759,10175 L 9759,10178 L 9759,10181 L 9758,10184 L 9756,10187 L 9754,10189 L 9751,10192 L 9748,10194 L 9744,10196 L 9734,10199 L 9722,10202 L 9708,10204 L 9674,10205 L 9634,10204 L 9589,10199 L 9539,10192 L 9487,10181 L 9435,10168 L 9388,10153 L 9345,10138 L 9309,10122 L 9279,10106 L 9267,10098 L 9257,10091 L 9250,10083 L 9247,10080 L 9245,10076 L 9243,10073 L 9242,10069 L 9242,10066 L 9242,10063 z"/><path d="M 6841,4401 L 6832,4382 L 6827,4362 L 6826,4339 L 6828,4314 L 6834,4288 L 6843,4260 L 6872,4199 L 6914,4133 L 6968,4061 L 7035,3985 L 7112,3904 L 7298,3734 L 7521,3555 L 7777,3372 L 8060,3190 L 8352,3022 L 8632,2879 L 8894,2763 L 9130,2676 L 9237,2644 L 9336,2621 L 9424,2606 L 9503,2599 L 9570,2601 L 9599,2606 L 9625,2613 L 9648,2622 L 9667,2633 L 9683,2648 L 9696,2664 L 9705,2683 L 9710,2703 L 9711,2726 L 9709,2751 L 9703,2777 L 9694,2805 L 9665,2866 L 9623,2932 L 9569,3004 L 9502,3080 L 9425,3161 L 9239,3331 L 9016,3510 L 8760,3693 L 8477,3875 L 8185,4043 L 7905,4186 L 7643,4302 L 7407,4389 L 7300,4420 L 7201,4444 L 7113,4459 L 7034,4466 L 6967,4464 L 6938,4459 L 6912,4452 L 6889,4443 L 6870,4432 L 6854,4417 L 6841,4401 z"/><path d="M 9098,6041 L 9075,6069 L 9049,6094 L 9020,6117 L 8989,6137 L 8956,6155 L 8920,6170 L 8843,6193 L 8757,6206 L 8665,6209 L 8567,6203 L 8463,6188 L 8354,6163 L 8242,6129 L 8127,6087 L 8009,6035 L 7890,5975 L 7770,5907 L 7651,5830 L 7532,5745 L 7418,5654 L 7311,5560 L 7212,5464 L 7122,5366 L 7040,5267 L 6968,5168 L 6904,5069 L 6851,4972 L 6808,4876 L 6775,4783 L 6753,4694 L 6742,4608 L 6743,4527 L 6748,4489 L 6755,4452 L 6766,4417 L 6780,4383 L 6798,4351 L 6818,4321 L 6841,4293 L 6867,4268 L 6896,4245 L 6927,4225 L 6960,4207 L 6996,4192 L 7073,4169 L 7159,4156 L 7251,4152 L 7349,4158 L 7453,4174 L 7562,4199 L 7674,4232 L 7789,4275 L 7907,4326 L 8026,4387 L 8146,4455 L 8265,4532 L 8384,4617 L 8498,4708 L 8605,4802 L 8704,4898 L 8794,4996 L 8876,5095 L 8948,5194 L 9012,5293 L 9065,5390 L 9108,5486 L 9141,5579 L 9163,5668 L 9174,5754 L 9173,5835 L 9168,5873 L 9161,5910 L 9150,5945 L 9136,5979 L 9118,6011 L 9098,6041 z"/><path d="M 7879,5222 L 7868,5212 L 7858,5199 L 7851,5184 L 7845,5167 L 7841,5147 L 7839,5126 L 7841,5077 L 7850,5020 L 7866,4957 L 7888,4888 L 7917,4812 L 7992,4647 L 8091,4466 L 8210,4274 L 8348,4075 L 8496,3883 L 8643,3712 L 8786,3563 L 8921,3442 L 8984,3392 L 9044,3349 L 9099,3316 L 9150,3290 L 9196,3274 L 9217,3269 L 9237,3267 L 9255,3267 L 9272,3270 L 9287,3275 L 9300,3283 L 9311,3293 L 9321,3306 L 9329,3321 L 9334,3338 L 9338,3358 L 9340,3379 L 9338,3428 L 9329,3484 L 9314,3547 L 9291,3617 L 9263,3692 L 9187,3858 L 9089,4039 L 8970,4231 L 8832,4430 L 8684,4621 L 8536,4793 L 8393,4941 L 8258,5063 L 8195,5113 L 8136,5155 L 8080,5189 L 8029,5214 L 7983,5231 L 7962,5235 L 7942,5238 L 7924,5238 L 7907,5235 L 7892,5230 L 7879,5222 z"/><path d="M 9004,6100 L 8984,6094 L 8965,6084 L 8948,6069 L 8931,6050 L 8915,6027 L 8901,6001 L 8875,5936 L 8854,5857 L 8837,5764 L 8825,5660 L 8818,5543 L 8817,5279 L 8834,4980 L 8871,4652 L 8927,4303 L 8999,3957 L 9082,3638 L 9173,3352 L 9269,3106 L 9317,2999 L 9366,2906 L 9415,2825 L 9463,2759 L 9510,2708 L 9533,2688 L 9556,2672 L 9578,2661 L 9600,2653 L 9621,2651 L 9642,2652 L 9662,2658 L 9681,2668 L 9699,2683 L 9715,2702 L 9731,2725 L 9746,2752 L 9771,2816 L 9793,2895 L 9809,2988 L 9821,3093 L 9829,3209 L 9830,3473 L 9812,3773 L 9775,4100 L 9719,4449 L 9647,4795 L 9564,5114 L 9473,5400 L 9377,5647 L 9329,5753 L 9280,5846 L 9231,5927 L 9183,5993 L 9136,6044 L 9113,6064 L 9090,6080 L 9068,6091 L 9046,6099 L 9025,6101 L 9004,6100 z"/></g></g></svg>

static/images/ok.svg<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width='32' height='32' viewBox='0 0 100 100'><circle cx='50' cy='50' r='40' fill='green'/></svg>
static/images/bad.svg<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width='32' height='32' viewBox='0 0 100 100'><circle cx='50' cy='50' r='40' fill='red'/></svg>
static/images/off.svg<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width='32' height='32' viewBox='0 0 100 100'><circle cx='50' cy='50' r='40' fill='black'/></svg>
static/images/yellow.svg<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width='32' height='32' viewBox='0 0 100 100'><circle cx='50' cy='50' r='40' fill='yellow'/></svg>
static/images/fork.svg<?xml version="1.0" encoding="utf-8"?>
<!-- Origin: https://seekicon.com/free-icon/fork_4 2022-07-01 License: SIL (OFL) -->
<!-- Generator: Adobe Illustrator 24.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve"><g><path d="M18,399.3h79.4V326c0-23.2,11.3-48.8,33-70.3C152,234.2,184.2,210.9,237,174c53.1-37.2,85.1-57.8,99.6-70.7 c14.4-12.9,13.2-10.8,13.2-29.6V0.9V0h63.1v0.9v72.7c0,28.2-11.8,56.6-34.2,76.6c-22.4,20-53.4,39-105.5,75.4 c-52.3,36.6-83.3,59.9-98.4,74.9c-15.1,15-14.3,15.6-14.3,25.5v73.4h82.8L130.7,512L18,399.3z M268.7,399.3h81.1V326 c0-9.8,0.8-10.5-14.3-25.5c-10-9.9-27.8-24-53.2-42.6c3.2-2.3,5.7-4.1,9.1-6.4c18.6-13,32.1-22,46-31.5 c17.7,13.4,31.5,24.8,42.5,35.7c21.7,21.4,33,47.1,33,70.3v73.4H494L381.4,512L268.7,399.3z"/></g></svg>