Skip to content

refactor(config): add a pure resolver alongside the viper loader#78

Merged
obcode merged 1 commit into
mainfrom
feat/pure-resolver
Jul 17, 2026
Merged

refactor(config): add a pure resolver alongside the viper loader#78
obcode merged 1 commit into
mainfrom
feat/pure-resolver

Conversation

@obcode

@obcode obcode commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Schritt 2b-1. Rein additiv — niemand ruft den neuen Resolver auf, der viper-Pfad ist unangetastet, kein bestehender Golden bewegt sich.

Warum geteilt

2b war der einzige Hochrisiko-Schritt des Plans. Statt Resolver bauen und umschalten in einem PR:

  • 2b-1 (hier): Resolve() neben dem viper-Pfad, plus ein differentieller Test, der beide gegeneinander hält. Null Risiko, und der Beweis entsteht vor dem Umschalten.
  • 2b-2 (nächster PR): umschalten, alten Pfad löschen. Der differentielle Test ist dann bereits grün.

Die tragende Entscheidung: Vererbung bleibt auf der rohen Map

Beim Entwerfen der Signatur bin ich auf eine Gabelung gestoßen, die der Plan überspringt. Ein dekodiertes Kind kann nicht ausdrücken „ich habe pipeline nicht erwähnt" — sein Feld ist so oder so false, und ein Struct-Merge ließe dieses false das true des Parents überschreiben. Abwesenheit überlebt nur als fehlender Map-Key, also muss der Merge vor dem Struct-Decode passieren.

Deshalb nimmt ResolveAssignment einen Course-Body, kein *CourseSource: das typisierte Quellschema und der Auflösungspfad sind zwei Zweige aus derselben rohen Map, keine Kette. (Genau so zeigt es auch das Plandiagramm — mir war beim Planen nicht klar, wie sehr das die Signatur bestimmt.)

Zwei Stellen schreiben Map-Keys explizit klein, mit Begründung im Code: viper tat das implizit für jeden geladenen Key, und Gruppen- und DeferredBranch-Namen sind Daten, keine Feldnamen. Sie landen in Repo-Namen und in glabs push mpd blatt07 devcontainer — die geschriebene Schreibweise zu behalten würde still ein anderes Projekt treffen.

Der Beweis

TestResolveMatchesViperLoader lässt beide Implementierungen über jedes Assignment jeder Fixture laufen und diffed die Ergebnisse — 60 Fälle, dazu Filter-Argumente und Fehlerpfade.

Das ist stärker als die Goldens: die pinnen einen aufgezeichneten Snapshot, dieser Test pinnt die zwei Implementierungen aneinander. Er wird zusammen mit dem alten Loader gelöscht.

Eine echte Lücke, gefunden durch Mutationsproben

Ich habe dem Test nicht geglaubt und ihn mutationsgetestet — den Resolver absichtlich kaputtgemacht und geschaut, ob es auffällt. Ergebnis: "generated by glabs""SABOTAGE" fiel niemandem auf.

Der Grund: jedes Assignment in jeder Fixture setzt eine description. Dasselbe galt für fast alle Defaults — Deine echten Kursdateien füllen praktisch alles aus, die Default-Zweige des Resolvers hatten null Abdeckung.

testdata/courses/defaults.yaml lässt konsequent jede optionale Einstellung weg und schließt die Lücke. Danach:

Default gefangen von
description generated by glabs 7 Subtests
startercode fromBranch main 2
templateMessage Initial 43
clone localpath . 38
release source develop 14
seeder toBranch main 31
issueNumbers [1] 1
accessLevel developer 55
deferredBranch orphan true 1

Nebenbei

config/signkey.go holt das Sign-Key-Parsen unter eigenen Namen — unverändert, nur verschoben. Es ist die einzige Stelle, an der Config-Laden auf stdin blockiert, und genau das steht zwischen config/ und der Benutzbarkeit aus einem Request-Handler. Das verdient einen eigenen Namen und Kommentar statt in einem Feld-Read vergraben zu sein.

Verifikation

  • go test ./... grün; gofmt, go vet, golangci-lint sauber
  • Kein bestehender Golden geändert → der Auflösungspfad ist nachweislich nicht angefasst
  • 60 differentielle Subtests + Filter + Fehlerpfade
  • Mutationsproben: alle 9 Defaults werden gefangen

🤖 Generated with Claude Code

Resolution currently runs on global viper state and writes back to it while
resolving `extends`, so a read path mutates a global registry and two courses
cannot be resolved concurrently. Add ResolveAssignment as a pure function of its
inputs, to be swapped in once it is proven equivalent.

Nothing calls it yet. The viper loader is untouched and no existing golden moves.

Inheritance stays on the raw map, and that is the load-bearing decision here. A
decoded child cannot express "I did not mention pipeline" — its field is false
either way, and a struct merge would let that false overwrite the parent's true.
Absence only survives as a missing map key, so the merge has to happen before the
struct decode. That is why ResolveAssignment takes a course body rather than a
*CourseSource: the typed source and the resolution path are two branches out of
the same raw map, not a chain.

Two places lowercase map keys explicitly, with a comment saying why: viper did it
implicitly for every key it loaded, and group and deferred-branch names are data,
not field names. They end up in repository names and in `glabs push mpd blatt07
devcontainer`, so keeping the written case would silently target a different
project.

TestResolveMatchesViperLoader runs both implementations over every assignment in
every fixture and diffs the results — 60 cases, plus filter arguments and error
paths. That is a stronger check than the goldens: goldens pin a recorded
snapshot, this pins the two implementations to each other. It is what makes the
swap provable rather than hopeful, and it gets deleted along with the loader.

Mutation-probing that test exposed a real gap: changing the default description
to a nonsense string failed nothing, because every assignment in every fixture
sets one. The same held for most defaults — the real course files fill nearly
everything in, so the resolver's default branches had no coverage at all. Adding
testdata/courses/defaults.yaml, which omits every optional setting, closes it:
all nine defaults are now caught by at least one test.

Also lifts the sign-key parsing into config/signkey.go, unchanged. It is the one
place config loading blocks on stdin, which is what stands between it and a
request handler — worth having under its own name and comment rather than buried
in a seeder field read.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Coverage

github.com/obcode/glabs/v2/cmd/addgroupguests.go:12:	init						100.0%
github.com/obcode/glabs/v2/cmd/archive.go:12:		init						100.0%
github.com/obcode/glabs/v2/cmd/check.go:10:		init						100.0%
github.com/obcode/glabs/v2/cmd/clone.go:48:		init						100.0%
github.com/obcode/glabs/v2/cmd/config.go:14:		init						100.0%
github.com/obcode/glabs/v2/cmd/config.go:117:		rewriteCourseFile				0.0%
github.com/obcode/glabs/v2/cmd/config.go:143:		decodeCourseFile				0.0%
github.com/obcode/glabs/v2/cmd/config.go:157:		coursesToProcess				0.0%
github.com/obcode/glabs/v2/cmd/config.go:170:		courseFilePath					0.0%
github.com/obcode/glabs/v2/cmd/delete.go:12:		init						100.0%
github.com/obcode/glabs/v2/cmd/generate.go:12:		init						100.0%
github.com/obcode/glabs/v2/cmd/protect.go:12:		init						100.0%
github.com/obcode/glabs/v2/cmd/push.go:38:		init						100.0%
github.com/obcode/glabs/v2/cmd/report.go:14:		init						100.0%
github.com/obcode/glabs/v2/cmd/root.go:39:		Execute						0.0%
github.com/obcode/glabs/v2/cmd/root.go:43:		init						100.0%
github.com/obcode/glabs/v2/cmd/root.go:51:		er						0.0%
github.com/obcode/glabs/v2/cmd/root.go:56:		initConfig					0.0%
github.com/obcode/glabs/v2/cmd/setaccess.go:12:		init						100.0%
github.com/obcode/glabs/v2/cmd/show.go:8:		init						100.0%
github.com/obcode/glabs/v2/cmd/update.go:12:		init						100.0%
github.com/obcode/glabs/v2/cmd/urls.go:34:		init						100.0%
github.com/obcode/glabs/v2/cmd/version.go:10:		init						100.0%
github.com/obcode/glabs/v2/config/assignment.go:11:	GetCourseURL					0.0%
github.com/obcode/glabs/v2/config/assignment.go:15:	GetAssignmentConfig				98.1%
github.com/obcode/glabs/v2/config/assignment.go:138:	RepoSuffix					100.0%
github.com/obcode/glabs/v2/config/assignment.go:157:	RepoBaseName					100.0%
github.com/obcode/glabs/v2/config/assignment.go:172:	RepoNameWithSuffix				100.0%
github.com/obcode/glabs/v2/config/assignment.go:176:	RepoNameForStudent				100.0%
github.com/obcode/glabs/v2/config/assignment.go:180:	RepoNameForGroup				100.0%
github.com/obcode/glabs/v2/config/assignment.go:184:	coursePath					100.0%
github.com/obcode/glabs/v2/config/assignment.go:195:	GetCourseSubgroupPath				0.0%
github.com/obcode/glabs/v2/config/assignment.go:199:	assignmentPath					100.0%
github.com/obcode/glabs/v2/config/assignment.go:210:	per						100.0%
github.com/obcode/glabs/v2/config/assignment.go:217:	description					100.0%
github.com/obcode/glabs/v2/config/assignment.go:227:	mergeRequest					94.4%
github.com/obcode/glabs/v2/config/assignment.go:271:	mergeRequestApprovals				95.9%
github.com/obcode/glabs/v2/config/assignment.go:351:	extractMergeRequestApprovalRulesRaw		100.0%
github.com/obcode/glabs/v2/config/assignment.go:364:	containsLegacyApprovalUsersKey			68.8%
github.com/obcode/glabs/v2/config/assignment.go:395:	mergeRequestApprovalSettings			95.8%
github.com/obcode/glabs/v2/config/assignment.go:442:	normalizeMergeRequestApprovalConfigKeys		100.0%
github.com/obcode/glabs/v2/config/course.go:15:		GetCourseConfig					100.0%
github.com/obcode/glabs/v2/config/course.go:28:		CourseExists					0.0%
github.com/obcode/glabs/v2/config/course.go:33:		StudentKey					0.0%
github.com/obcode/glabs/v2/config/decode.go:72:		DecodeCourse					100.0%
github.com/obcode/glabs/v2/config/decode.go:103:	DecodeCourseBody				87.5%
github.com/obcode/glabs/v2/config/decode.go:151:	decodeInto					75.0%
github.com/obcode/glabs/v2/config/decode.go:167:	prefixPaths					90.9%
github.com/obcode/glabs/v2/config/decode.go:188:	approvalsDecodeHook				100.0%
github.com/obcode/glabs/v2/config/decode.go:203:	normalizeLegacyKeys				100.0%
github.com/obcode/glabs/v2/config/encode.go:25:		EncodeCourse					66.7%
github.com/obcode/glabs/v2/config/inheritance.go:27:	assignmentIsAbstract				100.0%
github.com/obcode/glabs/v2/config/inheritance.go:48:	resolveAssignmentInheritance			100.0%
github.com/obcode/glabs/v2/config/inheritance.go:67:	mergedAssignmentMap				100.0%
github.com/obcode/glabs/v2/config/inheritance.go:104:	deepMerge					100.0%
github.com/obcode/glabs/v2/config/inheritance.go:125:	asStringMap					42.9%
github.com/obcode/glabs/v2/config/lint.go:29:		String						0.0%
github.com/obcode/glabs/v2/config/lint.go:39:		Lint						100.0%
github.com/obcode/glabs/v2/config/lint.go:68:		unknownKeyHint					75.0%
github.com/obcode/glabs/v2/config/lint.go:79:		lintAssignment					87.2%
github.com/obcode/glabs/v2/config/lint.go:177:		validWhenCommitAdded				66.7%
github.com/obcode/glabs/v2/config/lint.go:186:		sortedAssignmentNames				100.0%
github.com/obcode/glabs/v2/config/release.go:8:		release						100.0%
github.com/obcode/glabs/v2/config/release.go:22:	releaseMergeRequest				100.0%
github.com/obcode/glabs/v2/config/release.go:46:	dockerImages					100.0%
github.com/obcode/glabs/v2/config/repo.go:12:		startercode					100.0%
github.com/obcode/glabs/v2/config/repo.go:60:		branches					91.5%
github.com/obcode/glabs/v2/config/repo.go:141:		normalizeBranchRuleConfigKeys			100.0%
github.com/obcode/glabs/v2/config/repo.go:172:		defaultBranch					62.5%
github.com/obcode/glabs/v2/config/repo.go:187:		issues						100.0%
github.com/obcode/glabs/v2/config/repo.go:209:		clone						100.0%
github.com/obcode/glabs/v2/config/repo.go:231:		SetBranch					100.0%
github.com/obcode/glabs/v2/config/repo.go:235:		SetProtectToBranch				88.9%
github.com/obcode/glabs/v2/config/repo.go:253:		SetLocalpath					100.0%
github.com/obcode/glabs/v2/config/repo.go:257:		SetForce					100.0%
github.com/obcode/glabs/v2/config/resolve.go:34:	ResolveAssignment				85.7%
github.com/obcode/glabs/v2/config/resolve.go:76:	lookupAssignment				87.5%
github.com/obcode/glabs/v2/config/resolve.go:90:	reservedCourseKey				66.7%
github.com/obcode/glabs/v2/config/resolve.go:100:	truthy						75.0%
github.com/obcode/glabs/v2/config/resolve.go:113:	mergeAssignment					94.7%
github.com/obcode/glabs/v2/config/resolve.go:150:	buildAssignmentConfig				100.0%
github.com/obcode/glabs/v2/config/resolve.go:202:	resolvePer					100.0%
github.com/obcode/glabs/v2/config/resolve.go:209:	resolveDescription				100.0%
github.com/obcode/glabs/v2/config/resolve.go:216:	resolveAccessLevel				60.0%
github.com/obcode/glabs/v2/config/resolve.go:228:	resolveAssignmentPath				100.0%
github.com/obcode/glabs/v2/config/resolve.go:240:	ResolveCoursePath				0.0%
github.com/obcode/glabs/v2/config/resolve.go:248:	resolveStartercode				100.0%
github.com/obcode/glabs/v2/config/resolve.go:277:	resolveBranches					95.0%
github.com/obcode/glabs/v2/config/resolve.go:340:	resolveIssues					100.0%
github.com/obcode/glabs/v2/config/resolve.go:365:	resolveClone					100.0%
github.com/obcode/glabs/v2/config/resolve.go:380:	resolveRelease					86.7%
github.com/obcode/glabs/v2/config/resolve.go:410:	resolveDeferredBranches				100.0%
github.com/obcode/glabs/v2/config/resolve.go:452:	resolveMergeRequest				83.3%
github.com/obcode/glabs/v2/config/resolve.go:497:	resolveApprovalRules				100.0%
github.com/obcode/glabs/v2/config/resolve.go:529:	trimUnique					100.0%
github.com/obcode/glabs/v2/config/resolve.go:546:	trimNonEmpty					100.0%
github.com/obcode/glabs/v2/config/resolve.go:556:	resolveApprovalSettings				91.7%
github.com/obcode/glabs/v2/config/resolve.go:592:	resolveStudents					88.9%
github.com/obcode/glabs/v2/config/resolve.go:609:	resolveGroups					95.8%
github.com/obcode/glabs/v2/config/resolve.go:652:	filterByPatterns				100.0%
github.com/obcode/glabs/v2/config/resolve.go:667:	resolveSeeder					90.9%
github.com/obcode/glabs/v2/config/seeder.go:10:		seeder						100.0%
github.com/obcode/glabs/v2/config/show.go:10:		Show						93.1%
github.com/obcode/glabs/v2/config/signkey.go:26:	parseSignKey					33.3%
github.com/obcode/glabs/v2/config/slug.go:44:		gitlabProjectPath				100.0%
github.com/obcode/glabs/v2/config/slug.go:60:		gitlabGroupPath					100.0%
github.com/obcode/glabs/v2/config/slug.go:75:		gitlabGroupPathSegment				100.0%
github.com/obcode/glabs/v2/config/slug.go:93:		parameterize					100.0%
github.com/obcode/glabs/v2/config/students.go:14:	SetAccessLevel					100.0%
github.com/obcode/glabs/v2/config/students.go:32:	matchesPattern					100.0%
github.com/obcode/glabs/v2/config/students.go:37:	accessLevel					100.0%
github.com/obcode/glabs/v2/config/students.go:52:	students					100.0%
github.com/obcode/glabs/v2/config/students.go:84:	mkStudents					94.4%
github.com/obcode/glabs/v2/config/students.go:117:	groups						100.0%
github.com/obcode/glabs/v2/config/types.go:5:		String						100.0%
github.com/obcode/glabs/v2/config/urls.go:9:		StartercodeURL					60.0%
github.com/obcode/glabs/v2/config/urls.go:18:		gitURLToWebURL					56.0%
github.com/obcode/glabs/v2/config/urls.go:61:		Urls						100.0%
github.com/obcode/glabs/v2/git/auth.go:11:		GetAuth						90.0%
github.com/obcode/glabs/v2/git/clone.go:18:		Clone						80.0%
github.com/obcode/glabs/v2/git/clone.go:38:		ProjectRepoUrl					100.0%
github.com/obcode/glabs/v2/git/clone.go:44:		localpath					100.0%
github.com/obcode/glabs/v2/git/clone.go:48:		clone						36.1%
github.com/obcode/glabs/v2/git/push.go:17:		Push						0.0%
github.com/obcode/glabs/v2/git/sourcerepo.go:19:	PrepareSourceRepo				0.0%
github.com/obcode/glabs/v2/gitlab/approvals.go:15:	applyMergeRequestApprovalRules			100.0%
github.com/obcode/glabs/v2/gitlab/approvals.go:19:	applyMergeRequestApprovalRulesForMemberCount	80.7%
github.com/obcode/glabs/v2/gitlab/approvals.go:222:	applyMergeRequestApprovalSettings		73.8%
github.com/obcode/glabs/v2/gitlab/approvals.go:285:	approvalRuleName				60.0%
github.com/obcode/glabs/v2/gitlab/approvals.go:295:	approvalRuleAppliesForMemberCount		100.0%
github.com/obcode/glabs/v2/gitlab/approvals.go:305:	resolveApprovalUsernames			77.8%
github.com/obcode/glabs/v2/gitlab/approvals.go:337:	resolveApprovalGroupIDs				75.0%
github.com/obcode/glabs/v2/gitlab/approvals.go:364:	isNumericIdentifier				75.0%
github.com/obcode/glabs/v2/gitlab/archive.go:14:	Archive						77.8%
github.com/obcode/glabs/v2/gitlab/archive.go:32:	archivePerStudent				90.9%
github.com/obcode/glabs/v2/gitlab/archive.go:54:	archivePerGroup					90.9%
github.com/obcode/glabs/v2/gitlab/archive.go:76:	archive						75.6%
github.com/obcode/glabs/v2/gitlab/branches.go:11:	syncConfiguredBranches				50.0%
github.com/obcode/glabs/v2/gitlab/branches.go:47:	defaultBranchName				37.5%
github.com/obcode/glabs/v2/gitlab/branches.go:65:	isBranchAlreadyExistsError			0.0%
github.com/obcode/glabs/v2/gitlab/check.go:9:		CheckCourse					90.6%
github.com/obcode/glabs/v2/gitlab/check.go:67:		checkStudent					100.0%
github.com/obcode/glabs/v2/gitlab/check.go:89:		checkDupsInGroups				100.0%
github.com/obcode/glabs/v2/gitlab/delete.go:11:		Delete						77.8%
github.com/obcode/glabs/v2/gitlab/delete.go:29:		deletePerStudent				100.0%
github.com/obcode/glabs/v2/gitlab/delete.go:40:		deletePerGroup					100.0%
github.com/obcode/glabs/v2/gitlab/delete.go:51:		delete						92.9%
github.com/obcode/glabs/v2/gitlab/generate.go:14:	Generate					38.9%
github.com/obcode/glabs/v2/gitlab/generate.go:56:	generate					0.0%
github.com/obcode/glabs/v2/gitlab/generate.go:273:	generatePerStudent				0.0%
github.com/obcode/glabs/v2/gitlab/generate.go:286:	generatePerGroup				0.0%
github.com/obcode/glabs/v2/gitlab/gitlab.go:13:		NewClient					80.0%
github.com/obcode/glabs/v2/gitlab/groups.go:13:		getGroupIDByFullPath				100.0%
github.com/obcode/glabs/v2/gitlab/groups.go:34:		getGroupID					100.0%
github.com/obcode/glabs/v2/gitlab/groups.go:47:		createGroup					83.3%
github.com/obcode/glabs/v2/gitlab/groups.go:88:		AddGroupGuests					0.0%
github.com/obcode/glabs/v2/gitlab/groups.go:145:	inviteGroupByEmail				0.0%
github.com/obcode/glabs/v2/gitlab/groups.go:163:	addGroupMember					0.0%
github.com/obcode/glabs/v2/gitlab/groups.go:200:	collectUniqueStudents				0.0%
github.com/obcode/glabs/v2/gitlab/issues.go:99:		getStartercodeProject				100.0%
github.com/obcode/glabs/v2/gitlab/issues.go:143:	replicateIssue					37.0%
github.com/obcode/glabs/v2/gitlab/issues.go:202:	resolveIssuePlanForReplication			83.3%
github.com/obcode/glabs/v2/gitlab/issues.go:245:	resolveIssueNumbersForReplication		75.0%
github.com/obcode/glabs/v2/gitlab/issues.go:254:	loadIssueForReplication				69.6%
github.com/obcode/glabs/v2/gitlab/issues.go:298:	listChildIIDsByParentLookup			46.2%
github.com/obcode/glabs/v2/gitlab/issues.go:346:	listChildIIDsByIssueGraphQL			82.4%
github.com/obcode/glabs/v2/gitlab/issues.go:381:	workItemTypeIDForName				0.0%
github.com/obcode/glabs/v2/gitlab/issues.go:406:	attachChildTaskToParent				0.0%
github.com/obcode/glabs/v2/gitlab/issues.go:429:	getProjectPathForGraphQL			25.0%
github.com/obcode/glabs/v2/gitlab/projects.go:12:	generateProject					77.5%
github.com/obcode/glabs/v2/gitlab/projects.go:102:	getProjectByName				80.0%
github.com/obcode/glabs/v2/gitlab/protect.go:15:	ProtectToBranch					77.8%
github.com/obcode/glabs/v2/gitlab/protect.go:33:	protectBranch					100.0%
github.com/obcode/glabs/v2/gitlab/protect.go:37:	protectBranchForMemberCount			71.8%
github.com/obcode/glabs/v2/gitlab/protect.go:119:	hasProtectedBranches				100.0%
github.com/obcode/glabs/v2/gitlab/protect.go:129:	hasMergeRequestApprovalConfig			100.0%
github.com/obcode/glabs/v2/gitlab/protect.go:137:	protectSingleBranch				72.2%
github.com/obcode/glabs/v2/gitlab/protect.go:191:	recreateProtectedBranch				80.0%
github.com/obcode/glabs/v2/gitlab/protect.go:229:	replaceBranchPermissions			88.9%
github.com/obcode/glabs/v2/gitlab/protect.go:245:	isProtectedBranchNotFoundError			75.0%
github.com/obcode/glabs/v2/gitlab/protect.go:254:	protectToBranchPerStudent			90.9%
github.com/obcode/glabs/v2/gitlab/protect.go:276:	protectToBranchPerGroup				72.7%
github.com/obcode/glabs/v2/gitlab/push.go:10:		Push						0.0%
github.com/obcode/glabs/v2/gitlab/report.go:14:		Report						78.9%
github.com/obcode/glabs/v2/gitlab/report.go:51:		ReportHTML					73.7%
github.com/obcode/glabs/v2/gitlab/report.go:86:		ReportJSON					77.8%
github.com/obcode/glabs/v2/gitlab/report_helper.go:17:	report						82.5%
github.com/obcode/glabs/v2/gitlab/report_helper.go:135:	projectReport					85.9%
github.com/obcode/glabs/v2/gitlab/seeder.go:21:		localpath					0.0%
github.com/obcode/glabs/v2/gitlab/seeder.go:25:		runSeeder					0.0%
github.com/obcode/glabs/v2/gitlab/seeder.go:100:	addAndCommit					0.0%
github.com/obcode/glabs/v2/gitlab/seeder.go:121:	push						0.0%
github.com/obcode/glabs/v2/gitlab/setaccess.go:14:	Setaccess					77.8%
github.com/obcode/glabs/v2/gitlab/setaccess.go:32:	setaccess					41.5%
github.com/obcode/glabs/v2/gitlab/setaccess.go:140:	inviteByEmail					100.0%
github.com/obcode/glabs/v2/gitlab/setaccess.go:155:	setaccessPerStudent				100.0%
github.com/obcode/glabs/v2/gitlab/setaccess.go:175:	setaccessPerGroup				80.0%
github.com/obcode/glabs/v2/gitlab/starterrepo.go:16:	pushStartercode					0.0%
github.com/obcode/glabs/v2/gitlab/update.go:15:		Update						60.0%
github.com/obcode/glabs/v2/gitlab/update.go:49:		update						31.6%
github.com/obcode/glabs/v2/gitlab/update.go:96:		updatePerStudent				100.0%
github.com/obcode/glabs/v2/gitlab/update.go:116:	updatePerGroup					100.0%
github.com/obcode/glabs/v2/gitlab/users.go:12:		getUser						95.0%
github.com/obcode/glabs/v2/gitlab/users.go:49:		getUserID					100.0%
github.com/obcode/glabs/v2/gitlab/users.go:63:		addMember					94.1%
github.com/obcode/glabs/v2/main.go:19:			main						0.0%
total:							(statements)					68.7%

@obcode
obcode merged commit 9913954 into main Jul 17, 2026
10 checks passed
@obcode
obcode deleted the feat/pure-resolver branch July 17, 2026 14:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant