# Code access

> Give each team the GitHub and GitLab repositories its machines may read and push to - with no token ever in a machine - and take them away again in a moment.

Give each team the repositories on GitHub and GitLab its machines may read, and those they may push to. No token ever goes into a machine: a fetch works only for a repository the team may read, and a push only for one it may write - as the person who holds the machine, and never to a protected branch.

Connected code hosts, and a team given a repository:

```
wm code
wm code repos code_01j9z3...
wm code grant prj_01j9z3... code_01j9z3... acme/api write
```

## Connect GitHub

An organisation's owners and admins - or you, in your own account - install Wheelmachine's GitHub App on **Code**, **Connect GitHub**, and choose its repositories. Finish in a browser signed in to Wheelmachine as you (`wm code connect github` prints the same link). Add or remove repositories on GitHub at any time: a repository taken from the App is taken from every team given it.

## Connect GitLab

An organisation's owners and admins - or you, in your own account - connect gitlab.com in one approval: on the dashboard's **Code** page, **Connect GitLab**. GitLab asks you to sign in and to let Wheelmachine in; approve it and you are back on the Code page with GitLab connected - no token to make, copy or paste. Finish in the browser you started from, signed in to Wheelmachine as you. Its repositories are the GitLab projects you are a Developer, Maintainer or Owner of.

Disconnect it, and Wheelmachine revokes its access on GitLab too. Revoke it yourself on GitLab instead (your profile's **Applications**), and its repositories stop reaching the teams' machines; connect GitLab again with the same GitLab account, and the teams have back what they were given.

### Your company's own GitLab

For a GitLab your company runs itself, make a **group access token** in the group's Settings › Access tokens, with the role Developer and the scopes `read_repository` and `write_repository`, and add it on the **Code** page (**Use your company's own GitLab server**), with `wm code connect gitlab` or with `POST /v1/code/gitlab`. It is stored encrypted and never shown again; disconnect it to take it away.

## Give a team repositories

A team's full managers, the organisation's owners and admins - or you, in your own account - give it repositories of a connection, each to **read** (clone and fetch) or to **write** (push too). Everyone in the team sees what it was given. A machine reaches its team's repositories:

- while its holder is in the team - owners and admins are in every team;
- whatever its internet: open, closed, or with a list that names neither GitHub nor GitLab.

Take a repository away, or a person out of the team, and the team's machines lose it at once. A machine handed off reaches what its new holder's team was given, and pushes as its new holder.

## Link your own accounts

A machine's pushes carry the name of the person who holds it, so each person links their own GitHub and GitLab once: **Code**, **Your accounts**. A machine can clone and fetch without a link; a push without one is refused and says so.

Your link stays fresh by itself. Its tokens are stored encrypted, never in a machine. **Unlink** stops your machines' pushes there and revokes the link on GitHub or GitLab too. If you take the approval back on GitHub or GitLab yourself, your machines' pushes there stop, and the Code page offers **Link again** the next time Wheelmachine renews the link. On a GitLab connected by a group's token - your company's own - nobody's account is linked: pushes there go with the group's token.

## Protected branches and push rules

No machine pushes to a protected branch: changes reach it through a pull request on GitHub, or a merge request on GitLab. An organisation's owners and admins - or you, in your own account - choose the protected branches under **Protected branches** on the **Code** page, or with `PUT /v1/code/protected` and a `cloud:*` key of the whole account. Until they do, `main`, `master` and `release/**` are protected; they may protect none.

A team's full managers, and the owners and admins, can limit a team's pushes to some branches: **Push rules** on its row, **Only these branches**, such as `feature/**` or `fix/*`, and its tags or not. A team's rule narrows the protected branches, never widens them: a protected branch stays out whatever the team's rule says. Without a rule, "Read and push" is any branch but the protected ones, and any tag.

Patterns are GitHub's: `*` is any part of a name but `/`, `**` any part with `/` too, and `?` one character. `release/**` holds `release/1.0` and `release/eu/2`; `qa/*` holds `qa/one` but not `qa/one/two`. A push naming a protected branch answers "main is protected: changes reach it through a pull request" (a merge request, on GitLab); one outside the team's rule, "this team's machines may push only to feature/** or fix/*".

## In a machine

Clone, fetch and push as always, with the repository's https address:

shell:

```
git clone https://github.com/acme/api.git
git push
```

Git in a machine reaches your team's repositories with no token in the machine. A fetch uses the team's access. A push uses the holder's own linked account, and only to the branches the account and the team allow. For your company's own GitLab, run this once in the machine:

shell:

```
git config --global url."http://169.254.169.251:7703/gitlab.example.com/".insteadOf https://gitlab.example.com/
```

Everything else works as it did: public repositories, ones the team was not given (with your own sign-in), Git LFS, and `git@github.com:` addresses with your own SSH key. A push to a repository the team was given only to read answers 403, with words saying why. With its internet closed, or a list that does not name the host, a machine reaches its team's repositories and nothing else of GitHub or GitLab: no public repository, none the team was not given, no Git LFS. A closed machine can still push to its team's repositories, and a push can start that repository's own CI, which runs outside the machine with its own internet.

On GitLab, a push to a repository your team was given takes only the options that open a merge request: `merge_request.create`, `merge_request.target`, `merge_request.title`, `merge_request.description`, `merge_request.draft` and `merge_request.remove_source_branch`. Any other option is refused, and says so, so no option can merge or name another project. `[skip ci]` in a commit message or a merge request's title still skips a pipeline. A description with a line beginning with `/`, with spaces before it or not, is refused too, as GitLab would run it as a command: write `\/` to show one.

## Pull requests

A machine can open a pull request as the last step of its work. People merge it: this does not merge, approve or close anything.

What a machine reaches, and a pull request from it:

```
wm machines repos box
wm machines pull-request box acme/api --head agent/sign-in --base main --title "Sign in works again"
```

An agent has the same as `list_repositories` and `open_pull_request`. A key that runs commands (`cloud:machines:exec`) opens one; only the person who holds the machine may ask.

A pull request needs what a push needs: a repository the team may push to, the holder's own account linked (a GitLab connected by a group's token needs no link), and a machine whose internet reaches the host. Its head is a branch of the same repository that the team's machines may push to, so not a fork and not a protected branch. Its base may be a protected branch.

### On GitHub

It opens at once under the holder's own account, and answers its number and address. For one person and repository, at most 5 open a minute and about 60 an hour, with up to about 120 in a first hour; for one person, 10 a minute; for an account, 20 a minute. What GitHub refuses uses up none of those, but for one person at most 30 a minute are asked of GitHub at all, refused or not.

Pull requests need a permission Wheelmachine's GitHub App did not ask for at first. GitHub asks an owner of each account that installed the App to accept it, by e-mail and on the App's page in that account's settings. Until then, a pull request is refused with words saying so, and where to accept it.

### On GitLab

A merge request opens with a push, so nothing is open yet: the answer is a line of git to run in your clone, in the machine.

shell:

```
git push -o merge_request.create -o merge_request.target=main -o 'merge_request.title=Sign in works again' origin agent/sign-in
```

GitLab opens it, or updates the one open from that branch, and git prints its address. It goes under the holder's linked account, or on your company's own GitLab under its group's token. The push has to send new commits: if git says everything is up to date, make a commit first.

Line breaks in the description go as `\n`, so a `\n` written in the body becomes a line break too. A body with a line beginning with `/`, with spaces before it or not, is refused, as GitLab would run that line as a command: write `\/` to show one. So is a title, body or branch holding U+FFFD, the character that stands for unreadable text, as a machine's push may not carry it.
