Friday, 28 December 2018

GIT: Splitting Up a Git Repository



Here’s the situation: you have a Git repository and you want to split it up into smaller repositories. Perhaps you have an application that has grown to large and you need to break out parts of into their own application (as services) or maybe as their libraries.
Whatever your reason is, the task is the same: how do I get this big Git repository broken out into two (or more)? And how do I do that while also maintaining the history of the changes in the directory I’m splitting out?
That is our most important requirement: we don’t want to lose the history of the files that we’re splitting up from the repository.
Let’s go through the steps.

GIT: What is a bare Git repository?

The standard way of initializing a new Git repository is to run git init. The directory in which you do this will be become the Working Tree for the repository.
As part of the initialization process, Git creates a .git directory (which his hidden by default because of the . in the name) that contains the repository itself. This is brains of the repository; it's where Git tracks your changes, stores commit objects, refs, etc. You probably only rarely interact with that hidden directory.
Okay, so all of this is to lay the groundwork for understanding a bare Git repository. What the heck is it?
A bare Git repository is a repository that is created without a Working Tree. Go ahead and create one to see.
git init --bare .
Run ls on that directory and you won't see a Working Tree but just the contents of what is typically in the .git directory.
Why this setup?
A bare Git repository is typically used as a Remote Repository that is sharing a repository among several different people. You don't do work right inside the remote repository so there's no Working Tree (the files in your project that you edit), just bare repository data.
And that's it.

GIT: What is a Git Remote Repository

By default Git is completely local to your computer. You can optionally have remote copies of the repository (either on a central server or service—like Github—or on a co-workers computer).
To get your copy of the repository up to a remote server you use the command git-push. If you want to retrieve others’ changes to the repository you use git-pull.
A Remote Repository in Git is a special type of repository in that it doesn't have a Working Tree. This is different than your local repository, which has your project files and then a hidden .git directory.
You can host your own Git remote repository or use one the popular online services, like GithubGitlab, or Bitbucket.

GIT: What is the Working Tree in Git?

The Working Tree in Git is a directory (and its files and sub directories) on your file system that is associated with a repository.
It's full of the files you edit, where you add new files, and from which you remove unneeded files. Any changes to the Working Tree are noted by the Index (see below), and show up as modified files.
When you open the files for a project that is being managed as a Git repository then you are access the Working Tree.

GIT: Downloading Data from a Remote

The "fetch" command is indispensable for staying up-to-date in a project: only when performing a "git fetch" will you be informed about the changes your colleagues pushed to the remote server.

git fetch Updates Your View of Remote Data

Before we talk about "git fetch" itself it's really important to understand how remote data is managed in Git.
When looking at your remote branches and commits, you must keep in mind that you're looking at a "snapshot" of data. There is no "live" connection to your remote server.
Instead, Git saves the latest information about your remotes each time a "fetch" operation is performed. This means that the information about remote branches and commits that you see is only as fresh as the last snapshot you fetched.
To see new remote branches, commits, tags, etc., you have to explicitly tell Git to download this information - with "git fetch".

Fetch Does Not Integrate

Fetch is probably one of the most harmless commands in Git: although it downloads data to your local repository, it does not integrate any of that data into your local branches or working copy. Fetch really only updates your view on remote data.
It's up to you to later integrate any of the new data into your local project.

git fetch in Action

The actual syntax of a Fetch command is dead simple: "git fetch" and the name of the remote server is all we need.
$ git fetch origin
A useful parameter, however, is "prune": this tells Git to remove any references to branches that no longer exist on the remote. This ensures that you don't look at stale data:
$ git fetch origin --prune

GIT: git checkout a Remote Branch

One of the first Git commands you've learned was certainly "git checkout":
$ git checkout development
In its simplest form, it allows you to switch (and even create) local branches - something you need countless times in your day-to-day work.
However, git checkout's power is not limited to local branches: it can also be used to create a new local branch from a remote one.

Collaborating with Branches

Remember that branches are the main way of collaboration in Git. Let's say that one of your colleagues wants you to collaborate on (or review) a piece of code:
  1. She will push the corresponding branch to your common remote server.
  2. In order to see this newly published branch, you will have to perform a simple "git fetch" for the remote.
  3. Using the "git checkout" command, you can then create a local version of this branch - and start collaborating!

git checkout for Remote Branches

The syntax for making git checkout "remote-ready" is rather easy: simply add the "--track" flag and the remote branch's ref like in the following example:
$ git checkout --track origin/newsletter
Branch newsletter set up to track remote branch newsletter from origin.
Switched to a new branch 'newsletter'
Based on the remote branch "origin/newsletter", we now have a new local branch named "newsletter".
Note that, by default, Git uses the same name for the local branch. Being a good convention, there's rarely the need to change this.

Committing and Exchanging Data

From here on, you can make changes and commit them to your new local branch like you're used to.
Remember to publish your changes to the remote by using "git push" from time to time. Only then will your colleague(s) be able to see and understand your changes.

GIT: How to Integrate Branches

Separating different topics into different branches is a crucial practice for any serious developer. By not mixing up code from one feature / bugfix / experiment with another, you avoid a lot of problems - and don't have to worry about breaking things in your development branches.
But at some point your code will (hopefully) reach a state where you'll want to integrate it with the rest of the project. This is where the "git merge" command comes in.

A Simple Example

Let's assume a very simple example scenario:
Our goal is to integrate the changes from "contact-form" back into "master".

Preparing to Merge

Before merging your code into one of your project's long-running branches (like a "development" or "master" branch), make sure that your local repository is up to date. Both your local feature / bugfix / <topic> branch and the receiving branch should be updated with the latest changes from your remote server.
Start with a "git fetch", followed by a "git branch -va":
$ git fetch
...

$ git branch -va
  master             87eab46 [behind 1] Fix #332
* contact-form       b320ab3 Ensure safe login
The [behind 1] remark tells us that "master" has received new changes on the remote. We must update "master" before we can integrate our own changes.
If properly configured, a plain "git pull" should suffice (after making "master" our active branch):
$ git checkout master
$ git pull
The last thing to check before actually starting the merge process is our current HEAD branch: we need to make sure that we've checked out the branch that should receivethe changes.
But since we just updated "master" and already performed a "git checkout master", we're good to go!

Starting the Merge

With such perfect preparation, the actual merge process itself is easy as pie:
$ git merge contact-form
Looking at our project's commit history, we'll notice that a new commit was created: a so-called "merge commit" that represents the actual "melting knot" that combines the two branches.
Our integration was successful and, if our feature work on "contact-form" is finished, we could safely delete that branch.

Dealing with Conflicts

Git will do what it can to make merging as easy as in our example. And in many cases, a merge will indeed be a walk in the park.
In some cases, however, the integration will not go as smoothly: if the branches contain incompatible changes, you will have to face (and solve) a "merge conflict". If you want to learn more about how to handle such a situation, have a look at Dealing with Merge Conflicts.