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:
- 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.
- 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
- Install Perl and NASM. Make sure that they are both on the PATH, such that the
perl
andnasm
commands can be run from the build directory. - Download and extract the OpenSSL source distribution. Metro has been tested with version 1.1.1f.
- 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.
- Switch to the OpenSSL source directory with
cd
. - If you want to build 32-bit Metro, run
perl Configure -static VC-WIN32
. If you want to build 64-bit Metro, runperl Configure -static VC-WIN64A
. Make sure that the version you choose matches the command prompt you have open from step 3. - Run
nmake
. This might take some time, but once done there should be two files calledlibssl.lib
andlibcrypto.lib
in the OpenSSL directory. There should be no.dll
files; if there are you likely forgot the-static
flag above. - 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
- Install Homebrew if you do not have it already.
- Run
brew install openssl
. - Set the
OPENSSL_ROOT_DIR
environment variable to$(brew --prefix openssl)
. - 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