gocov

module
v0.8.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 10, 2026 License: AGPL-3.0

README

gocov

Self-hostable coverage tracking — an open-source Coveralls/Codecov alternative. Single binary + Postgres. Supported forges: Bitbucket Cloud and GitHub. Supported formats: Go cover profiles, LCOV tracefiles (JavaScript/TypeScript — Jest, Vitest, nyc, c8), JaCoCo XML (Java/Kotlin — Maven, Gradle, Android) and Cobertura XML (Python — coverage.py/pytest-cov; also PHPUnit, coverlet, gcovr); the format is detected from the uploaded content.

Features (MVP)

  • Parses Go cover profiles (go test -coverprofile), LCOV tracefiles (Jest/Vitest/nyc lcov.info), JaCoCo XML (jacoco.xml) and Cobertura XML (coverage.xml) into total and per-file coverage

  • POST /api/v1/upload API with per-repo Bearer tokens

  • SVG coverage badge per repo (/badge/{workspace}/{repo}.svg)

  • Web UI: repo list → upload list → per-file coverage table

  • Uploader CLI that auto-detects Bitbucket Pipelines and GitHub Actions environment variables and falls back to git

  • Pushes a coverage: X% (±Y%) build status to Bitbucket commits (or a commit status to GitHub) when the repo has forge credentials configured

  • Coverage gate: per-repo minimums for total and diff coverage plus a drop tolerance; violations push a FAILED build status, so a Bitbucket merge check can block the PR

  • Source view: any file in an upload renders line by line with coverage overlay and hit counts, fetched from the forge at the exact commit and cached immutably (misses are cached too); without forge credentials the page falls back to an uncovered-line summary. When an upload has no path_prefix, recorded paths that carry an unmapped leading prefix (a Go module path, a CI checkout directory) are resolved by probing trimmed variants against the forge

  • Web UI sign-in with Bitbucket and/or GitHub: configure an OAuth consumer/app and every page requires login, allowed only for members of the workspaces and orgs the instance tracks (see "Enable sign-in"). Uploads, badges and health checks are unaffected; no passwords are ever stored

  • Diff coverage for pull requests: fetches the PR diff from the forge, intersects changed lines with coverage blocks, and posts a PR comment listing uncovered changed lines — repeated uploads update the same comment instead of stacking new ones. Works on Bitbucket and GitHub alike

  • Coverage inside the PR, via Bitbucket Code Insights: every upload attaches a report card to its commit (total coverage, delta, diff coverage, gate verdict) that Bitbucket shows in the pull request's Reports panel, and PR uploads annotate uncovered changed lines right in the diff view — reviewers see untested code exactly where they are reviewing it, with no plugin on the Bitbucket side. Changed files with no coverage data at all get a file-level marker, and the report card lists the worst-covered changed files while the field budget allows. Re-uploads replace the report and annotations in place; no coverage product on Bitbucket Cloud ships this today

    [screenshot: coverage report card and inline annotations in a Bitbucket PR]

  • On GitHub the same surface ships as a check run named gocov coverage: a summary card with the identical data, a conclusion that mirrors the coverage gate (success/failure, neutral without a gate) so branch protection can require it, and inline annotations on uncovered changed lines in the Files changed view. Re-uploads replace the run in place. Writing check runs needs a credential GitHub allows to (see "GitHub token permissions"); with other tokens the check run is skipped while status, comment and gate keep working

  • Coverage trend chart: the repo page graphs total coverage over the branch's recent uploads (gate failures marked in red, every point links to its upload) — rendered as inline SVG on the server, no JavaScript chart library

    [screenshot: coverage trend chart on a repo page]

The architecture is deliberately extensible: coverage formats sit behind profile.Parser, forges behind forge.Forge, raw profile storage behind blobstore.Store, and the database schema stores a format-agnostic normalized model — so lcov/cobertura, GitHub/GitLab, diff coverage, and S3 storage can be added without rewrites.

Quick start

docker compose up

This starts Postgres and the server on http://localhost:8080 (migrations apply automatically).

For many repos, register the workspace once and use a single token:

docker compose exec server gocov-server workspace add \
  -prefix myworkspace -default-branch main

Set the printed token as a Bitbucket workspace variable (GOCOV_TOKEN, secured) together with GOCOV_SERVER — every repo inherits them. Repos register themselves on their first upload; their default branch is asked from Bitbucket when a global bot account is configured (see Configuration), falling back to the workspace's -default-branch.

Registering repos one by one
docker compose exec server gocov-server repo add \
  -slug myworkspace/myrepo \
  -default-branch main \
  -bb-username myuser -bb-app-password "$APP_PASSWORD"   # optional, for build statuses

For a GitHub repo, the slug is owner/repo and the credential is a single access token (see "GitHub token permissions"):

docker compose exec server gocov-server repo add \
  -slug myorg/myrepo -forge github \
  -default-branch main \
  -gh-token "$GITHUB_TOKEN"                              # optional, for statuses and PR comments

A GitHub org can also be onboarded wholesale: workspace add -prefix myorg -forge github — repos then register themselves on first upload, exactly like a Bitbucket workspace.

Manage repos later with:

gocov-server repo list                                   # slugs, branches, credential status
gocov-server repo rotate-token -slug myworkspace/myrepo  # invalidates the old token
gocov-server repo update -slug myworkspace/myrepo \
  -default-branch develop                                # and/or -bb-username/-bb-app-password
                                                         # or -gh-token, or -clear-credentials
gocov-server repo remove -slug myworkspace/myrepo -force # deletes uploads and raw profiles too;
                                                         # without -force only prints a summary
gocov-server workspace list|rotate-token|update|remove   # workspace token management
Enable sign-in (Bitbucket and/or GitHub)

Out of the box the web UI is open and shows a banner saying so — nothing changes on upgrade until you opt in. Configure one or both providers; each renders as its own button on the login page.

For Bitbucket:

  1. Create an OAuth consumer under Workspace settings → OAuth consumers → Add consumer with
    • Callback URL: https://your-gocov-host/oauth/bitbucket/callback (must be exactly GOCOV_BASE_URL + /oauth/bitbucket/callback)
    • Permissions: Account: Read and Email only — nothing broader is needed
  2. Set the consumer's key and secret on the server:
GOCOV_OAUTH_BITBUCKET_KEY=...
GOCOV_OAUTH_BITBUCKET_SECRET=...

For GitHub:

  1. Create an OAuth app under Settings → Developer settings → OAuth Apps → New OAuth App (on your account or org) with
    • Authorization callback URL: https://your-gocov-host/oauth/github/callback
  2. Set the app's client id and secret on the server:
GOCOV_OAUTH_GITHUB_KEY=...
GOCOV_OAUTH_GITHUB_SECRET=...

gocov requests the read-only read:org and user:email scopes at login. Note that org members may need to grant/request the app's access to the org once (GitHub's third-party application policy) for the org to appear in their membership.

From then on every UI page requires signing in. Access is decided at login time by membership: by default, members of any workspace/org the instance tracks (registered workspaces and the workspace part of registered repo slugs) may sign in, and everyone else gets a clear denial page; on GitHub the account's own username also counts, so user-namespace repos admit their owner. Set GOCOV_ALLOWED_WORKSPACES (comma-separated workspace/org slugs) to replace the derived set with an explicit list. Accounts are provisioned on first successful sign-in — there is no user bookkeeping, and gocov never sees or stores passwords (the forge tokens are discarded right after login).

CI is unaffected either way: the upload API keeps its Bearer tokens, badges stay embeddable, /healthz stays open.

Hosted mode (self-service signup)

GOCOV_MODE=hosted turns the instance into a self-service one: any forge account may sign in, and a user who belongs to no tracked workspace lands on /register, which lists the workspaces their forge account is a member of (captured at sign-in). Claiming one creates the workspace, makes the user a member and shows the upload token — once; afterwards it can only be rotated. Only workspaces the forge itself reports for the account can be registered, so there is nothing to dispute: if a colleague registered your workspace first, signing in simply makes you a member.

Registration lands on an onboarding page: the forge-appropriate CI snippet with the server URL and token pre-filled, and a live "waiting for your first upload" state that flips to the repo link once coverage arrives.

The default (GOCOV_MODE=private) keeps exactly the behavior described above — self-hosted deployments upgrade with zero change. Hosted mode requires at least one sign-in provider.

Workspace settings in the UI

Signed-in members manage their workspaces from the dashboard (private and hosted mode alike): rotate the upload token (the old one dies immediately; the new one is shown once), change the default branch and gate defaults for auto-registered repos, and set a workspace-level bot credential used for statuses, PR comments and insights on repos without their own. Stored secrets are never rendered back — the page only shows whether a credential is configured. The CLI (gocov-server workspace ...) keeps working; the UI is an addition, not a migration.

gocov-server user list                          # who has signed in
gocov-server user remove -email jane@example.com  # revoke immediately

Removal deletes the account and its sessions; the person can sign in again (and is re-provisioned) as long as they are still a workspace member. Sessions last 30 days; membership is re-checked at each login, not per request.

Access mirrors your forge workspace membership. Once sign-in is configured, each account sees only the repos in the workspaces and orgs the forge says it belongs to — the repo list is filtered, and a direct link to another workspace's repo, upload or source page returns 404. Memberships are synced from the forge on every sign-in, so there is no separate invite or member-management step: add someone to the workspace on Bitbucket or GitHub and they see its coverage at their next login; remove them and it disappears. A single-team self-host where everyone belongs to the same workspace is unaffected, as is an instance with sign-in left open — both stay exactly as before.

GitHub App (one-click connect)

Instead of manufacturing a token, GitHub workspaces can install a GitHub App: one click on GitHub and statuses, PR comments and check runs work with zero credential entry, authored by the app's bot identity (e.g. gocov[bot]). The App is also the first-class Checks API citizen, so check runs stop being permission-fragile.

To run one on your own deployment:

  1. Register a GitHub App (Settings → Developer settings → GitHub Apps → New GitHub App) with
    • Setup URL: https://your-gocov-host/github/setup, with Redirect on update enabled
    • Webhook: disabled (gocov's model is upload-driven; installs are linked through the setup redirect and uninstalls are detected lazily)
    • Repository permissions: Checks: Read & write, Commit statuses: Read & write, Pull requests: Read & write, Contents: Read-only, Metadata: Read-only
    • Organization permissions: Members: Read-only (org membership for sign-in sync)
  2. Generate a private key on the app page and set both variables on the server:
GOCOV_GITHUB_APP_ID=...
GOCOV_GITHUB_APP_PRIVATE_KEY=/path/to/gocov.private-key.pem  # or the PEM content itself

Members then connect from the workspace settings or setup page ("Install the gocov app"); after GitHub's install screen they land back on gocov with the workspace connected. In hosted mode the install can even come first — an install on an account with no workspace yet registers it on the spot (same claim rules as /register).

A connected installation sits at the top of the credential chain: it outranks per-repo and workspace credentials for every forge surface. Uninstalling the app on GitHub is detected on the next upload — the affected surfaces degrade to the stored credentials, or to skipped, never to a failed upload — and the settings page offers a reconnect. The token paths below keep working untouched; the App is an addition, not a migration.

Bitbucket workspace connect (one-click)

Bitbucket workspaces get the same effortless path: a member clicks Connect workspace on the settings (or setup) page, consents once on Bitbucket, and statuses, PR comments, reports, diffs and source fetch work with zero manual credentials from then on. To enable it, the deployment needs the sign-in OAuth consumer plus an encryption key:

GOCOV_SECRET_KEY=...   # long random secret (e.g. `openssl rand -hex 32`); encrypts the stored grant at rest

The AES key is derived from this value with a plain SHA-256 — there is no slow KDF to compensate for a guessable passphrase — so generate it randomly rather than inventing one.

and the consumer's permissions extended beyond sign-in: Account: Read, Email, Repositories: Write, Pull requests: Write. (Bitbucket scopes live on the consumer, not the consent request, so the sign-in consent lists them too — sign-in itself still stores no forge tokens.)

Honest caveat, stated in the UI at connect time: Bitbucket has no app identity, so posts appear as the account that clicked Connect. Teams with a bot account should sign the bot in and connect with it.

The grant's refresh token is stored on the workspace, AES-GCM-encrypted under GOCOV_SECRET_KEY; access tokens live only in memory. Bitbucket rotates refresh tokens on every use — gocov persists each rotation atomically. If the grant dies (the connecting account leaves the workspace, the consent is revoked under Personal settings → Authorized applications, or the token ages out after three unused months), the next upload degrades to the stored credentials or to skipped, never to a failure, and the settings page offers a reconnect. The app-password / API-token paths keep working untouched.

Coverage gate
gocov-server repo update -slug myworkspace/myrepo \
  -min-coverage 80 -min-diff-coverage 70 -max-drop 0.5

Each rule is optional: -min-coverage is the minimum total percentage, -min-diff-coverage applies to the changed lines of PR uploads (skipped when no diff coverage is available), and -max-drop bounds how far total coverage may fall below the latest gate-passing upload on the default branch (0 forbids any drop). Gate-failing uploads are recorded but never serve as a baseline, so re-running CI cannot launder a failure and a PR cannot ratchet coverage down push by push. Violations mark the pushed build status FAILED — require the gocov build in Bitbucket's merge checks to block such PRs — and are reported in the PR comment and the upload response (gate field). -clear-gate removes all rules. The same flags on workspace add and workspace update set defaults inherited by auto-registered repos. The CLI exits non-zero on a failed gate when run with -fail-on-gate.

Uploading coverage from CI

In Bitbucket Pipelines (commit, branch, repo and PR id are auto-detected):

- step:
    script:
      - go test ./... -covermode=atomic -coverprofile=coverage.out
      - go run github.com/bykclk/gocov/cmd/gocov@latest upload coverage.out

with GOCOV_SERVER and GOCOV_TOKEN set as repository variables.

In GitHub Actions (commit, branch, repo and PR number are auto-detected, including the PR head SHA on pull_request runs):

- run: go test ./... -covermode=atomic -coverprofile=coverage.out
- run: go run github.com/bykclk/gocov/cmd/gocov@latest upload coverage.out
  env:
    GOCOV_SERVER: ${{ vars.GOCOV_SERVER }}
    GOCOV_TOKEN: ${{ secrets.GOCOV_TOKEN }}

On runners without a Go toolchain, use the prebuilt binaries from GitHub Releases instead (linux/darwin/windows, amd64 + arm64, checksums included). Pin a version and cache the download on self-hosted runners:

ver=v0.1.0
arch=$(uname -m); case "$arch" in x86_64) arch=amd64;; aarch64|arm64) arch=arm64;; esac
bin="$HOME/.cache/gocov/gocov-$ver-linux-$arch"
if [ ! -x "$bin" ]; then
  mkdir -p "$(dirname "$bin")"
  curl -fsSL "https://github.com/bykclk/gocov/releases/download/$ver/gocov-linux-$arch" -o "$bin"
  chmod +x "$bin"
fi
"$bin" upload coverage.out

Outside CI, values fall back to git or can be passed explicitly:

gocov upload -server https://gocov.example -token $TOKEN \
  -repo myworkspace/myrepo -commit $(git rev-parse HEAD) -branch main \
  coverage.out

Other ecosystems upload their reports the same way — the format is detected from the content, no flag needed:

npx jest --coverage             # or vitest run --coverage, nyc, c8 ...
gocov upload coverage/lcov.info

mvn verify                      # with the jacoco-maven-plugin
gocov upload target/site/jacoco/jacoco.xml

gradle test jacocoTestReport    # xml.required = true
gocov upload build/reports/jacoco/test/jacocoTestReport.xml

pytest --cov --cov-report=xml   # coverage.py / pytest-cov
gocov upload coverage.xml

JaCoCo paths are package-qualified (com/example/Foo.java); diff coverage matches them against repo paths by suffix, so source roots like src/main/java need no configuration.

Badge

![coverage](https://gocov.example/badge/myworkspace/myrepo.svg)

Red below 50%, yellow 50–75%, green above 75%. Shows the latest upload on the repo's default branch.

API

POST /api/v1/upload — multipart form, Authorization: Bearer <token>

part meaning
profile file: the coverage profile
repo optional; must match the token's repo
commit required commit SHA
branch defaults to the repo's default branch
pr_id optional pull request id
format go, lcov, jacoco or cobertura; omitted → detected from content
path_prefix maps profile paths to repo paths for diff coverage, e.g. the Go module path (the CLI fills it from go.mod)

Returns 201 with {id, total_pct, covered_stmts, total_stmts, delta_pct, build_status}. Uploads carrying a pr_id additionally get diff_pct, diff_covered_lines, diff_total_lines, diff_status and pr_comment when the repo has forge credentials configured.

Configuration

variable default
DATABASE_URL — Postgres DSN (required)
GOCOV_ADDR :8080 listen address
GOCOV_BASE_URL http://localhost:8080 public URL used in statuses
GOCOV_BITBUCKET_USERNAME — global Bitbucket bot account (with an API token, the account email)
GOCOV_BITBUCKET_APP_PASSWORD — the bot's app password or scoped API token
GOCOV_GITHUB_TOKEN — global GitHub token for repos without their own credentials
GOCOV_GITHUB_APP_ID — GitHub App id; with the key, enables one-click workspace connect
GOCOV_GITHUB_APP_PRIVATE_KEY — the App's private key: PEM content, or a path to the PEM file
GOCOV_SECRET_KEY — at-rest encryption key (long random value, e.g. openssl rand -hex 32); with the Bitbucket OAuth consumer, enables one-click workspace connect
GOCOV_OAUTH_BITBUCKET_KEY — Bitbucket OAuth consumer key; with the secret, turns on web UI sign-in
GOCOV_OAUTH_BITBUCKET_SECRET — Bitbucket OAuth consumer secret
GOCOV_OAUTH_GITHUB_KEY — GitHub OAuth app client id; with the secret, turns on web UI sign-in
GOCOV_OAUTH_GITHUB_SECRET — GitHub OAuth app client secret
GOCOV_ALLOWED_WORKSPACES derived from tracked repos comma-separated workspace/org slugs allowed to sign in
GOCOV_MODE private hosted opens sign-in to any forge account with self-service workspace registration

Forge credentials resolve per repo along a precedence chain: a one-click connection (GitHub App installation, or Bitbucket workspace grant) beats per-repo credentials (repo update -bb-username ... / -gh-token ...) beats workspace credentials (set on the workspace settings page in the UI) beats the global bot credentials above. Whichever wins is used for build statuses, PR comments, diff coverage and default branch detection.

Bitbucket token permissions

Workspaces connected through the one-click grant need none of this. The manual bot credential (a scoped API token; Bitbucket removed app passwords in July 2026) needs:

capability API token scopes app password checkboxes
build status, Code Insights report + annotations, source view, default branch read:repository:bitbucket, write:repository:bitbucket Repositories: Read, Write
PR diff coverage, PR comment read:pullrequest:bitbucket, write:pullrequest:bitbucket Pull requests: Read, Write
updating the PR comment in place read:user:bitbucket Account: Read

Without the account/user scope everything still works, but gocov cannot recognize its own earlier comment, so every upload posts a new PR comment instead of updating the existing one. If comments stack, this scope is the fix.

The OAuth consumer used for web UI sign-in is separate and needs the Account: Read and Email permissions on the consumer itself.

GitHub token permissions

Workspaces connected through the GitHub App need none of this — the App's own permissions cover every surface, check runs included. The token path remains for repos outside a connected workspace:

The GitHub credential (GOCOV_GITHUB_TOKEN or repo add/update -gh-token) is a personal access token of a user or bot account with access to the repos:

  • Classic token: the repo scope covers commit statuses, PR comments, PR diffs, file content and the default branch. Public repos get by with public_repo. GitHub does not let classic tokens write check runs — those uploads report code_insights: skipped while everything else keeps working.
  • Fine-grained token: grant the repositories with Contents: Read (file content, default branch, PR diffs), Commit statuses: Write (build status), Pull requests: Write (PR comment) and Checks: Write (the check run with inline annotations).

GitHub documents check-run writes as a GitHub App capability; a fine-grained token with Checks: Write is the closest a plain credential gets, and coverage has historically had gaps on some repo types. If the check run is skipped for you, the commit status and PR comment still carry the full verdict.

Unlike Bitbucket, updating the PR comment in place needs no extra account scope — gocov recognizes its own comment by its **gocov** marker.

To make the coverage gate blocking on GitHub, add a branch protection rule under Settings → Branches → Require status checks to pass and pick gocov (the commit status — works with every credential) or gocov coverage (the check run). A failed gate then blocks the merge.

Development

go test ./...
go build ./...

The store, forge and blobstore interfaces each have test doubles (internal/store/memory, internal/forge/fake, internal/blobstore/memory), so handlers are fully testable without Postgres or a forge.

The Postgres store additionally has integration tests that run against a real server when GOCOV_TEST_DATABASE_URL is set (they are skipped otherwise). Each test creates and drops its own scratch database:

docker run --rm -d --name gocov-test-db -p 5433:5432 \
  -e POSTGRES_USER=gocov -e POSTGRES_PASSWORD=gocov -e POSTGRES_DB=gocov \
  postgres:16-alpine
GOCOV_TEST_DATABASE_URL=postgres://gocov:gocov@localhost:5433/gocov go test ./...
docker stop gocov-test-db

GET /healthz reports readiness (checks database connectivity) for load balancers and container orchestrators; the server shuts down gracefully on SIGINT/SIGTERM.

License

AGPL-3.0 — see LICENSE.

Directories

Path Synopsis
cmd
gocov command
gocov is the coverage uploader CLI, run inside CI:
gocov is the coverage uploader CLI, run inside CI:
gocov-preview command
Command gocov-preview is a throwaway dev harness: it serves the web UI from an in-memory store seeded with a synthetic upload history, for eyeballing UI changes without Postgres.
Command gocov-preview is a throwaway dev harness: it serves the web UI from an in-memory store seeded with a synthetic upload history, for eyeballing UI changes without Postgres.
gocov-server command
gocov-server runs the gocov API + web UI, and provides repo administration:
gocov-server runs the gocov API + web UI, and provides repo administration:
internal
auth
Package auth defines the sign-in provider seam for the web UI.
Package auth defines the sign-in provider seam for the web UI.
auth/bitbucket
Package bitbucket implements auth.Provider for Bitbucket Cloud using only the standard library.
Package bitbucket implements auth.Provider for Bitbucket Cloud using only the standard library.
auth/github
Package github implements auth.Provider for GitHub (cloud github.com) using only the standard library.
Package github implements auth.Provider for GitHub (cloud github.com) using only the standard library.
blobstore
Package blobstore abstracts raw-profile persistence so it can later move to object storage (S3/R2) without touching callers.
Package blobstore abstracts raw-profile persistence so it can later move to object storage (S3/R2) without touching callers.
blobstore/memory
Package memory provides an in-memory blobstore.Store for tests.
Package memory provides an in-memory blobstore.Store for tests.
blobstore/postgres
Package postgres implements blobstore.Store on a Postgres bytea column.
Package postgres implements blobstore.Store on a Postgres bytea column.
diffcov
Package diffcov computes diff coverage: which changed lines of a pull request are covered by tests.
Package diffcov computes diff coverage: which changed lines of a pull request are covered by tests.
forge
Package forge abstracts VCS-host integrations (Bitbucket first; GitHub and GitLab later).
Package forge abstracts VCS-host integrations (Bitbucket first; GitHub and GitLab later).
forge/bitbucket
Package bitbucket implements forge.Forge for Bitbucket Cloud.
Package bitbucket implements forge.Forge for Bitbucket Cloud.
forge/fake
Package fake provides a recording forge.Forge test double.
Package fake provides a recording forge.Forge test double.
forge/github
Package github implements forge.Forge for GitHub (cloud github.com).
Package github implements forge.Forge for GitHub (cloud github.com).
profile
Package profile defines the normalized coverage model shared by every coverage format, and the Parser interface that format-specific parsers implement.
Package profile defines the normalized coverage model shared by every coverage format, and the Parser interface that format-specific parsers implement.
secretbox
Package secretbox seals short secrets for at-rest storage with AES-256-GCM (One-Click Connect D6).
Package secretbox seals short secrets for at-rest storage with AES-256-GCM (One-Click Connect D6).
server
Package server implements the gocov HTTP API, badge endpoint and web UI.
Package server implements the gocov HTTP API, badge endpoint and web UI.
store
Package store defines the storage interface and its domain types.
Package store defines the storage interface and its domain types.
store/memory
Package memory provides an in-memory store.Store for tests.
Package memory provides an in-memory store.Store for tests.
store/postgres
Package postgres implements store.Store on PostgreSQL via pgx.
Package postgres implements store.Store on PostgreSQL via pgx.
testpg
Package testpg provides disposable Postgres databases for integration tests.
Package testpg provides disposable Postgres databases for integration tests.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL