Banner

What is Metro?

Metro is an alternative to the git command that lets you work with your Git repos in a more simple and flexible way, and makes it much easier to sync unfinished work between machines. Having learned from Git's shortcomings, Metro has two central design philosophies that differentiate it from pure Git:

  1. The repository is a tool to work on and transfer code between computers, not just an archive for finished code: Metro stores the very latest version of your code in the repo, allowing you to easily transfer it between machines without making a half-finished commit every time you have to leave in a hurry.
  2. When things conflict, create new branches to resolve the issue rather than showing arcane error messages: Git is only meant for archiving finished code, so of course we wouldn't want several ugly conflicting branches cluttering up our beautiful archive; instead Git expects the issue to be fixed before changing the repo, which often results in users scouring Stack Overflow to find a complicated solution that involves internal parts of Git they've never heard of. Metro, on the other hand, doesn't have this issue; the repository is a tool for working on the code, so why not fix issues there too? When conflicts occur Metro will offer to make separate branches for each version, making it simple and intuitive to fix the problem yourself. And you always are given the option to resolve quickly by absorbing if you prefer.

Switching from Git?

See this page explaining the differences between Metro and Git in more depth.

Building

Metro has been built and tested on Windows, Linux and macOS. It depends on libgit2 and libssh2 on all platforms, and additionally OpenSSL on Windows and macOS. It also requires CMake 3.10 or later to build on any platform.

The guides below will walk you through building Metro and all of its dependencies from source on each platform. Alternatively you could install the dependencies via another method, such as your system package manager, however these versions may be too outdated to work with Metro.

Building for Windows
Building for Linux
Building for macOS

Building for Windows

These instructions assume you already have Visual Studio installed, and therefore have access to the Developer Command Prompt, or the x64 Native Tools Command Prompt on 64-bit systems.

1. Build OpenSSL

  1. Install Perl and NASM. Make sure that they are both on the PATH, such that the perl and nasm commands can be run from the build directory.
  2. Download and extract the OpenSSL source distribution. Metro has been tested with version 1.1.1f.
  3. If you want to build a 32-bit version of Metro, open the Developer Command Prompt. If you want to build a 64-bit version of Metro, open the x64 Native Tools Command Prompt. It is essential that you use the right command prompt, otherwise you will experience build errors.
  4. Switch to the OpenSSL source directory with cd.
  5. If you want to build 32-bit Metro, run perl Configure -static VC-WIN32. If you want to build 64-bit Metro, run perl Configure -static VC-WIN64A. Make sure that the version you choose matches the command prompt you have open from step 3.
  6. Run nmake. This might take some time, but once done there should be two files called libssl.lib and libcrypto.lib in the OpenSSL directory. There should be no .dll files; if there are you likely forgot the -static flag above.
  7. Set the OPENSSL_ROOT_DIR environment variable to the path of the OpenSSL directory, which contains the .lib files.

2. Clone Metro

Clone using the command git clone --recursive https://github.com/SiliconSloth/Metro

Alternatively you can clone normally and use git submodule update --init --recursive to get dependancies

3. Build Metro

Switch into the Metro directory and run the following commands:

mkdir build
cd build
cmake ..
cmake --build .

This should create the metro.exe file in build.

4. Add to PATH

If you want to be able to run Metro from any directory on your computer, add the directory containing metro.exe to your path.

You can do this by going to Windows Explorer, right clicking This PC, choosing properties, choosing Advanced System Settings, choosing Environmental Variables, clicking on PATH in either User or System, choosing Edit and adding an entry with C:/path-to-metro/build

Building for Linux

1. Clone Metro

Clone using the command git clone --recursive https://github.com/SiliconSloth/Metro

Alternatively you can clone normally and use git submodule update --init --recursive to get dependancies

2. Build Metro

Switch into the Metro directory and run the following commands:

mkdir build
cd build
cmake ..
cmake --build .

This should create the metro file in build.

3. Add to PATH

If you want to be able to run Metro from any directory on your computer, add the directory containing metro to your path.

You can do this by adding export PATH=PATH:/path-to-metro/build to ~/.bashrc or similar

Building for macOS

1. Install OpenSSL

  1. Install Homebrew if you do not have it already.
  2. Run brew install openssl.
  3. Set the OPENSSL_ROOT_DIR environment variable to $(brew --prefix openssl).
  4. Set the LDFLAGS environment variable to -L$OPENSSL_ROOT_DIR/lib.

2. Clone Metro

Clone using the command git clone --recursive https://github.com/SiliconSloth/Metro

Alternatively you can clone normally and use git submodule update --init --recursive to get dependancies

3. Build Metro

Switch into the Metro directory and run the following commands:

mkdir build
cd build
cmake ..
cmake --build .

This should create the metro file in build.

4. Add to PATH

If you want to be able to run Metro from any directory on your computer, add the directory containing metro to your path.

You can do this by adding export PATH=PATH:/path-to-metro/build to $HOME/.bash_profile or similar

Commands

Basic Commands

metro create [directory]

Creates a new repository with an initial commit. The repository will be created in a new subdirectory with the specified name, or the current directory if none is specified.

metro clone <url>

Clones a remote repository from a specified URL. It is recommended that you use this instead of git clone, as it will initialize the sync cache so that metro sync works correctly.

metro commit <message>

Commits all changes in the working directory with the specified message. Note that unlike Git you do not need to add changes to the index first.

metro patch [message]

Adds the current changes to the previous commit. Will also change the commit message if one is specified.

metro delete commit

Deletes the previous commit, reverting the contents of the working directory to match the commit before the deleted commit. If --soft is specified the working directory is left unchanged.

metro delete branch <branch>

Deletes the specified branch. If you delete the current branch, you will be moved to another branch.

metro branch <name>

Creates a new branch with the specified name, pointing to the head of the current branch. Automatically switches to the new branch upon creation.

metro info

Prints some information about the current state of the repository, including the current branch and uncommitted changes.

metro absorb <branch>

Merges another branch into the current branch. May result in conflicts that need to be resolved manually before running metro resolve.

metro resolve

Marks all merge conflicts as resolved and commits the changes.

metro list <commits/branches>

Lists all commits or branches in the repository.

metro rename <branch-1> [branch-2]

Renames the specified branch. branch-1 is the current branch name, and branch-2 is the new name. If only one name is given the current branch is renamed.

Switching Branches

metro switch <branch>

The switch command allows you to move to a different branch or commit hash. If you have uncommitted changes in your working directory, these will be stored in a temporary WIP branch until you return to that branch. Each branch can have its own WIP branch. You can even switch branches during a merge; the merge will be saved to the WIP branch and continued when you switch back.

Note that uncommitted changes cannot be saved when switching away from a detached head (e.g. when you have a commit hash checked out rather than a branch name). switch will not normally allow you to switch away from a detached head with uncommitted changes; to override this behaviour and switch anyway, use --force. This will cause the uncommitted changes to be lost.

There are also some other conditions under which switch will fail, such as if a WIP branch already exists. These cases should never occur during regular use, but if you do experience such issues --force can be used to switch anyway, with potential loss of data. For more advanced ways to resolve issues with WIP branches, see the wip command.

Syncing with a Remote Repository

metro sync

The sync command synchronizes all your local branches with the remote repository, by either pushing or pulling each branch so that all changes since the last sync are retained on both sides. If a branch has been changed both locally and remotely since the last sync, Metro automatically creates a new branch to store one of these versions, so that you can merge or discard the changes at your leisure. Newly created or deleted branches will also be synced too, as will uncommitted changes (including ongoing merges) in the working directory.

Specify --pull to only pull branches, without pushing any local changes to the remote repository. Specify --push to only push branches to the remote, without pulling any changes. sync --push will fail if there are branch conflicts.

The sync command may fail if the repository has invalid WIP branches. If you experience issues pertaining to WIP branches, try using the wip command to resolve them.

Work In Progress Branches

Git can only sync data stored as a commit, so Metro automatically commits uncommitted changes to a temporary WIP branch when switch and sync are used. There can be a WIP branch for each base branch in the repository; the name of the WIP branch is the name of the base branch with the suffix #wip appended. When you switch or sync a branch, Metro will automatically restore the changes in the WIP branch to your working directory.

If Metro is used correctly, these WIP branches are mostly invisible to the user. However if regular Git commands are used on a repository, the WIP branches may be left in an invalid state. The wip command can be used to resolve such issues.

metro wip save

Saves the contents of the working directory to the current branch's WIP branch, in similar manner to switch and sync. Changes should not be made to the base branch until the WIP branch is restored. Syncing will still work in this state.

The command will fail if the WIP branch already exists. The existing WIP branch can be deleted with the standard metro delete branch command.

metro wip restore

Replaces the contents of the working directory with the contents of the WIP commit, overriding any existing uncommitted changes. This also deletes the WIP branch.

metro wip squash

Commits the content of the head of the WIP branch to a single commit that is a child of the base branch's head. This is useful if commits have been made on top of the WIP branch, rendering it invalid; restore allows all these commits to be converted into uncommitted changes in a single valid WIP commit.

Metro vs Git

What's wrong with Git?

Gets used for file syncing when it probably shouldn't be

Git repositories are meant to be a place to store finished parts of your code, in a manner that makes it easy for multiple people to work on different features in parallel. While Git is an excellent choice for this purpose, we feel that its approach is outdated and fails to effectively fulfil the needs of the modern programmer.

Git was designed for the days when programmers would write all their code at one computer, and only commit and push it to the repo once they had finished a feature completely. However, nowadays it is common for people to use many different computers (laptop, work computer, home desktop...) and want to switch machines partway through writing a feature. The correct way to solve this problem is probably to use a separate directory syncing tool to transfer the work-in-progress code, using the Git repo only for completed commits, however in practice most people just end up committing random half-finished changes when they need to leave in a hurry. This can lead to Git repositories filled with lots of poor-quality commits, with actual completed feature commits few and far between.

In contrast, Metro is made for syncing unfinished code between computers. Rather than having to commit halfway through a semicolon every time you leave the room, Metro syncs the current contents of the working directory into the repo so that you can easily transfer your work to another machine without leaving loads of broken commits in the repo. Metro treats the repo as a living, changing thing that includes the very latest versions of your code, rather than just the finished archived stuff.

When it breaks, it BREAKS

When you know the basics of Git (and remember to add your files before committing, and to push after committing, and to pull before working on the code) it is a simple and easy system to use. Unfortunately, when something goes wrong, it really does go wrong.

Here is common scene when trying to commit a file:

> git add -A

> git commit -m "Changed file"
[master 6c3a3de] Changed file
 1 file changed, 1 insertion(+), 1 deletion(-)

> git push
To github.com:Black-Photon/Git-Testing.git
 ! [rejected]        master -> master (fetch first)
error: failed to push some refs to 'git@github.com:Black-Photon/Git-Testing.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

> git pull
<Goes to vim to edit commit message>
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
From github.com:Black-Photon/Git-Testing
   4436cfd..f88f912  master     -> origin/master
Merge made by the 'recursive' strategy.
 Readme.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

> git push
Enumerating objects: 9, done.
Counting objects: 100% (8/8), done.
Delta compression using up to 4 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (5/5), 588 bytes | 588.00 KiB/s, done.
Total 5 (delta 0), reused 1 (delta 0)
To github.com:Black-Photon/Git-Testing.git
   f88f912..4129e5e  master -> master

Having to do so much is difficult to learn and not at all intuitive. Why do you need to add files? What does -m do? In what order do I pull and push? It also takes up time working out all the commands each time. Now what if that was:

> metro commit "Changed file"
1 file modified
Saved commit to branch master.

> metro sync
Fetching all branches from remote...
Branch master had remote changes that conflicted with yours; your commits have been moved to master#1.
You've been moved to master#1.

Features

We've tried to reduce the number of inconveniences you encounter with git as much as possible while still keeping the functionality you need:

  • As a Git wrapper, Metro is fully compatible with all Git tools
  • No staging area or adding files - all files are automatically added to commits
  • Push and pull combined into one command to sync the current line with the remote
  • Patching last commit with current work to fix mistakes or reduce small commits