MMotor OS
Developing

gix: the Git client

The developer image comes with gix, a small Git client, at /devtools/bin/gix. It works with normal Git repositories: a worktree with a .git directory, full history and SHA-1 object names. Gix reads and writes the same files as Git does, so you can copy a repository between Motor OS and a machine that has Git and keep working on either side. Just let gix finish what it is doing before you copy.

Gix has fewer commands than Git, and some of them are stricter than Git's. The program is always called gix; there is no git alias.

Getting started

Gix has to know who you are before it can write a commit. Put your name and email into ~/.gitconfig, or into the repository's .git/config:

[user]
    name = Your Name
    email = [email protected]

A first session looks like this: clone a repository, start a branch, change some files, and commit them.

gix clone https://example.test/project.git
gix -r project checkout -b topic
# Edit files in project, then:
gix -r project status
gix -r project add -A
gix -r project diff --staged
gix -r project commit -m 'Describe the change'

gix clone makes a new directory named after the repository, as Git does, so the clone above ends up in project. You can give another name after the URL. The directory must not exist yet. The remote is recorded as origin.

To start a repository of your own, run gix init DIR. The files already in DIR are left alone. The first branch is called main, or whatever init.defaultBranch says. Gix will not initialize a directory that is already a repository.

Gix works on the repository in the current directory, which has to be the top directory of the worktree: gix does not search the parent directories. Use -r PATH to work on a repository somewhere else. File names you pass to a command are taken literally (no wildcards) and are relative to that top directory. Put -- in front of a name that starts with a dash. gix COMMAND --help shows the exact syntax of a command.

Commands

CommandWhat it does
init [DIR]Create a new repository.
clone URL [DIR]Clone over HTTPS or SSH. DIR defaults to the repository name.
fetch [REMOTE]Download new commits and tags from a remote, origin by default. Your current branch and your files do not change.
statusShow what has changed, and whether a merge or another operation is unfinished.
logShow the history of the current branch.
diff [--staged] [PATH…]Show your unstaged changes, or with --staged what the next commit will contain. Binary files and mode changes are summarized.
add PATH…, add -AStage the named files, or with -A every new, changed and deleted file. -A skips new files that match an ignore rule; naming such a file yourself is an error.
unstage PATH…Take files out of the next commit without touching the files themselves.
restore PATH…Throw away your unstaged changes to the named files.
commit -m MSGCommit what is staged to the current branch.
branch [-a] [-v]List branches.
branch list, branch create NAME [REV]Print the branch names only, or create a branch without switching to it.
tag list, tag create NAME [REV]List tags, or create a lightweight tag.
remote [-v]List the remotes.
switch BRANCH, checkout BRANCHMove to another local branch.
checkout [--track] -b NEW [START]Create a branch and move to it.
merge REV, merge --abortMerge a commit into the current branch, or give up on a merge.
recoverFinish or undo an operation that was interrupted.
push [OPTIONS] REMOTE SOURCE:DESTINATIONPublish one branch or tag over SSH.

A few things work differently from Git:

Branches

gix branch prints the same list as git branch, with a * in front of the current branch. With -a it also lists the remote branches, such as remotes/origin/main. With -v every line also shows the short commit ID, the first line of the commit message, and how the branch compares with its upstream: [ahead 2], [behind 1], [ahead 2, behind 1], or [gone] if the upstream branch no longer exists. gix remote prints the remote names, and gix remote -v adds the URLs used for fetching and pushing, as git remote -v does.

branch create and tag create take a name and an optional commit, which defaults to HEAD. They never replace a branch or tag that already exists.

switch BRANCH and checkout BRANCH do the same thing: they move you to a local branch that already exists. Both need a clean repository, meaning nothing staged and no changes to tracked files. checkout also prints what Git prints, for example Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded.

checkout -b NEW [START] creates the branch NEW and moves you to it. START is where the branch begins; it defaults to HEAD.

Configuration

Gix reads Git's configuration files in this order: the XDG Git configuration, ~/.gitconfig and the files it includes, the repository's .git/config, and finally the -c key=value options on the command line. Run a command with --config-paths to see which files were read. HOME and XDG_CONFIG_HOME say where your own configuration lives.

Some settings are fixed and no configuration can change them: the ones that concern the Motor OS filesystem, and the size limits listed below. Gix does not accept the GIT_DIR, GIT_WORK_TREE and GIT_INDEX_FILE environment variables; use -r instead.

Gix never runs other programs on a repository's behalf. Hooks, credential helpers, external filters, diff and merge drivers and signing programs are all ignored. If a repository cannot be handled without one of them, for example a file that needs an external filter, the command stops with an error instead of guessing. The only programs gix starts are /system/bin/curl for HTTPS and /user/bin/ssh for SSH, and their paths cannot be configured.

Remotes

HTTPS

You can clone and fetch over HTTPS without logging in. The server's certificate is always checked against the system's trusted certificates. If your server uses a private certificate authority, name it on the command line:

gix -c http.sslCAInfo=/path/to/ca.pem clone https://git.example.test/project.git

A repository's own configuration cannot replace the trusted certificates or turn the check off. HTTPS with a login, pushing over HTTPS, plain HTTP, and cloning from a local path do not work.

SSH

SSH addresses can be written as ssh://git@host/path or as git@host:path. Before you use one, set up your key in /user/cfg/ssh/id_ed25519 and the server's key in /user/cfg/ssh/known_hosts with the SSH tools. Gix never asks questions: if the key is missing or the host is unknown, the command fails.

Pushing

Push goes over SSH only. You name the remote, by its configured name or by its SSH URL, and exactly one source and destination. If the remote has a pushurl, gix uses it. For example, to publish a branch from the HTTPS clone above:

gix -r project push --dry-run [email protected]:project.git HEAD:refs/heads/topic
gix -r project push [email protected]:project.git HEAD:refs/heads/topic

--dry-run contacts the server and checks that the push would be allowed, but changes nothing.

The source is a local branch or tag, HEAD, or a full 40-character object ID. The destination is a full name under refs/heads/ or refs/tags/, and a branch has to point to a commit.

Gix will create a new branch or tag, and it will fast-forward a branch. Anything else, such as rewriting a branch or replacing a tag, needs --force-with-lease=DESTINATION:OID, where OID is the commit the remote branch has right now. This protects you from overwriting work you have not seen. An empty OID means "only if the destination does not exist yet". A lease is checked even when there is nothing to push. If gix cannot tell whether your push is a fast-forward because it does not have the remote's commit, fetch first, or give a lease.

Push has no default destination, cannot push several branches at once, cannot delete, and has no plain --force. It does not update your origin/… branches either; run fetch afterwards.

Gix never repeats a network operation on its own. If a push ends with an unknown result, look at the branch on the server before you push again. If the server accepted the push, gix says so even when something goes wrong afterwards, for instance while cleaning up.

Merging

Like switch, merge needs a clean repository: nothing staged and no changes to tracked files. Both also stop if an untracked or ignored file is in the way of a file they have to write.

If REV is already part of your branch, there is nothing to do. Otherwise merge REV fast-forwards when it can. If both sides have new commits and they do not collide, gix creates a merge commit with two parents. Gix refuses to merge histories that have nothing in common.

If the two sides changed the same lines of a text file, gix writes conflict markers into the file and marks it as conflicted. Fix the file, run add on it, and then commit -m MSG. Or run merge --abort to give up. Any other kind of conflict, for example one side deleting a file that the other changed, makes gix refuse the merge before it touches your files.

merge --abort discards the merge, everything staged, and the changes to the files the merge touched. It keeps your unstaged changes to other files, and it keeps untracked and ignored files. restore and unstage do not work while a merge is in progress.

When something goes wrong

If a switch, a merge or an abort is interrupted, by an error or by Ctrl+C, the repository is left half-way. gix status tells you so but does not fix it, and gix refuses to change the repository any further. Fix whatever caused the problem (a full disk, for instance), then run gix recover.

recover normally puts the repository back the way it was before the interrupted command. If the command had already moved the branch, recover keeps that result and only finishes the cleanup. If a merge commit was not written, recover keeps your staged conflict resolutions, so you can still commit the merge or abort it. When it has to put files back, it discards the same things as merge --abort. If recover itself fails, it keeps its record of the interrupted command, so you can run it again. A merge that is waiting for you to resolve conflicts is not a job for recover; commit it or abort it.

A few more things to know:

What gix does not do

Limits

WhatLimit
Index file16 MiB
Text diff262,144 lines on each side
A loose reference file8 MiB
One HTTPS response, all the data of one SSH session, or one outgoing push pack128 MiB each
SSH session8 MiB for the list of references, 64 KiB of error output, 30 seconds to connect, 300 seconds in total
One push65,536 objects, 65,536 commits, 131,072 parent links, and 65,536 changes between any two trees

As in Git, there is no limit on the size of a single file or object. Gix holds each one in memory in full while it receives, checks out, stages, diffs or pushes it. What limits the size of a repository are the totals: a pack file can be at most 128 MiB and hold 65,536 objects, indexing a received pack can use 512 MiB of buffers in all, and the files of one commit can add up to 128 MiB.

The program's source is under src/bin/gix/ in the repository.