Skip to content

Contribution manager

CgContributionManager owns one contribution working directory. Unlike a puzzle, a contribution has many editable files that can each change on the server while you're working, so it's backed by a real git repository and has a real merge workflow.

from pathlib import Path
from codingame_tools.client import CgClient
from codingame_tools.contribution_manager import CgContributionManager

async with CgClient() as client:
    manager = CgContributionManager(Path("./contribution"), client)
    await manager.import_("1493730...")
    for result in await manager.play():
        print(result.ordinal, result.side, "PASS" if result.passed else "FAIL")
    await manager.push()

Lifecycle

create(title=...) A brand new, purely local working directory. No network, nothing server-side yet.
import_(handle, ...) Build from an existing server-side contribution, cover image included.
repair() Rebuild the git-dir from scratch without disturbing data/.
push() Send content to the server, then advance the internal branches.
delete() Delete from the server — unrecoverable — and by default remove the directory.

create() being local-only is the useful property: you can start a contribution, change your mind, and delete the directory having published nothing. The first push() then safely two-steps into createContribution.

The three branches

.meta/'s git repo carries more than your work:

main your working tree — what you're editing
server the server's current content, as last fetched
version-data an orphan branch with one commit per server version, holding the full redacted CgContribution

Three branches are what make a genuine three-way merge possible: your edits, the server's, and a common ancestor.

None of it is a backup. .meta/ is gitignored, disposable, and rebuilt by repair(). The only durable copy of a contribution's solution is on the server, which stores exactly one with no history — every push() overwrites it.

Syncing

fetch() Update server/version-data. Touches nothing you're editing.
rebase() Reconcile when unambiguous: no-op if the server hasn't moved, fast-forward if you have no local edits.
merge_start() / merge_continue() / merge_abort() A real git merge against the working tree, when it is ambiguous.
discard_local() Reset to server's tip exactly.

merge_continue() refuses if a file still contains conflict markers.

Running tests

for test_case in manager.list_local_tests():
    result = await manager.run_local_test(test_case, "Python3")

Entirely local. Worth doing before every push: updateContribution validates the reference solution against every test case server-side and rejects the whole push if any disagree.

The empty-solution rule

A zero-length data/solution.<ext> means "no reference solution" and is pushed as a null solutionSource. This isn't a quirk — it's required. updateContribution skips solution validation entirely when the solution is null but validates any non-null one against every test case, so a language with no working stub must leave the file empty rather than write a placeholder that would fail validation and block the push.

Test cases are rendered strings

data/tests/ holds files this client renders from strings the server stores, with a final newline added that is not part of the value. Everything that reads them converts back — play, the debugger, and push. Getting that wrong is a one-byte divergence that line-oriented solutions don't notice; see final newlines.