diff --git a/Documents/bin/git-namespace-backup b/Documents/bin/git-namespace-backup
index 61e33774b3f50cb78c9017cf8dbd0278a8fca4e6..4a3186f58679fce8a1f1d2cef87d801514ed3b4a 100755
--- a/Documents/bin/git-namespace-backup
+++ b/Documents/bin/git-namespace-backup
@@ -14,11 +14,14 @@ import pathlib
 import sys
 import os
 import os.path
-import re
-import gitlab  # pylint: disable=import-error
 import sh  # pylint: disable=import-error
 from sh.contrib import git  # pylint: disable=import-error
 
+sys.path.append(os.path.expanduser("~/Documents/bin"))
+
+import rcfiles.git  # noqa: E402 pylint: disable=wrong-import-position
+import rcfiles.gitlab  # noqa: E402 pylint: disable=wrong-import-position
+
 
 def namespace_path(namespace):
     """Returns the full path to the namespace.
@@ -35,92 +38,55 @@ def namespace_path(namespace):
     return path
 
 
-def name_to_path(name):
-    """Converts a name to valid path in GitLab."""
-    return re.sub("[^a-zA-Z0-9_-]", "-", name).lower()
+def list_repositories(namespace):
+    """Returns a list of paths under the namespace that are Git
+    repositories."""
+    return [x for x in namespace.iterdir() if rcfiles.git.is_repo(x)]
 
 
-def get_group(lab, name):
-    """Returns a GitLab group object.
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument("namespace", type=namespace_path)
+    args = parser.parse_args()
 
-    If a group with that name already exists, return that. Otherwise create a
-    new group and return that instead.
-    """
-    for i in lab.groups.list():
-        if i.name == name:
+    with rcfiles.gitlab.connect() as conn:
+        group = rcfiles.gitlab.get_group(conn, args.namespace.name)
+        if group is None:
+            group = rcfiles.gitlab.create_group(
+                conn, args.namespace.name, visibility="internal"
+            )
             print(
-                "Using existing group id: {}, name: {}, path: {}.".format(
-                    i.id, i.name, i.path
+                "Created new group id: {}, name: {}, path: {}.".format(
+                    group.id, group.name, group.path
                 ),
                 file=sys.stderr,
             )
-            return i
-    new_group = lab.groups.create(
-        {
-            "name": name,
-            "path": name_to_path(name),
-            "visivility": "internal",
-        }
-    )
-    print(
-        "Created new group id: {}, name: {}, path: {}.".format(
-            new_group.id, new_group.name, new_group.path
-        ),
-        file=sys.stderr,
-    )
-    return group
-
-
-def get_project(group, name):  # pylint: disable=redefined-outer-name
-    """Returns a GitLab project.
-
-    If a project with that name already exists, return that. Otherwise create a
-    new project and return that instead.
-    """
-    for i in group.projects.list():
-        if i.name == name:
+        else:
             print(
-                "Using existing project id: {}, name: {}, path: {}.".format(
-                    i.id, i.name, i.path
+                "Using existing group id: {}, name: {}, path: {}.".format(
+                    group.id, group.name, group.path
                 ),
                 file=sys.stderr,
             )
-            return i
-    new_project = group.projects.gitlab.projects.create(
-        {"name": name, "namespace_id": group.id}
-    )
-    print(
-        "Created new project id: {}, name: {}, path: {}.".format(
-            new_project.id, new_project.name, new_project.path
-        ),
-        file=sys.stderr,
-    )
-    return project
-
-
-def is_git_repo(path):
-    """Returns a boolean if the path is a Git repo."""
-    return path.is_dir() and pathlib.Path(path, ".git").is_dir()
-
-
-def list_repositories(namespace):
-    """Returns a list of paths under the namespace that are Git
-    repositories."""
-    return [x for x in namespace.iterdir() if is_git_repo(x)]
-
-
-TOKEN = os.environ["GITLAB_PRIVATE_TOKEN"]
-URL = os.environ["GITLAB_BASE_URL"].removesuffix("api/v4")
-
-if __name__ == "__main__":
-    parser = argparse.ArgumentParser(description=__doc__)
-    parser.add_argument("namespace", type=namespace_path)
-    args = parser.parse_args()
-
-    with gitlab.Gitlab(url=URL, private_token=TOKEN) as Lab:
-        group = get_group(Lab, args.namespace.name)
         for repo in list_repositories(args.namespace):
-            project = get_project(group, repo.name)
+            project = rcfiles.gitlab.get_project(conn, group.name, repo.name)
+            if project is None:
+                project = rcfiles.gitlab.create_project(
+                    conn, repo.name, group.name
+                )
+                print(
+                    "Created new project id: {}, name: {}, path: {}.".format(
+                        project.id, project.name, project.path
+                    ),
+                    file=sys.stderr,
+                )
+            else:
+                print(
+                    "Using existing project id: {}, name: {}, path: {}.".format(  # noqa: E501
+                        project.id, project.name, project.path
+                    ),
+                    file=sys.stderr,
+                )
             with sh.pushd(repo):
                 if "shore.co.il" in git.remote().splitlines():
                     print(
diff --git a/Documents/bin/rcfiles/git.py b/Documents/bin/rcfiles/git.py
new file mode 100644
index 0000000000000000000000000000000000000000..ff92a94c84c7430e53cbaa83214b371976771f64
--- /dev/null
+++ b/Documents/bin/rcfiles/git.py
@@ -0,0 +1,20 @@
+"""Git repository related functions."""
+
+import os.path
+import pathlib
+
+
+def is_repo(path):
+    """Returns a boolean if the path is a Git repo."""
+    return os.path.isdir(path) and pathlib.Path(path, ".git").is_dir()
+
+
+def in_repo():
+    """Is the current working directory a git repo?
+
+    Because we invoke the command as a Git command (git foo) it is run from
+    the root of the repository if inside a repository so there's no need to
+    traverse up the directory hierarchy to find if we're in a Git repository,
+    it's enough to just check if the .git directory exists where we are.
+    """
+    return is_repo(".")
diff --git a/Documents/bin/rcfiles/gitlab.py b/Documents/bin/rcfiles/gitlab.py
new file mode 100644
index 0000000000000000000000000000000000000000..f7ba6d9ad59be49704665c863e423c5c8938dcb8
--- /dev/null
+++ b/Documents/bin/rcfiles/gitlab.py
@@ -0,0 +1,79 @@
+"""A bunch of convenience functions to deal with GitLab."""
+
+import os
+import re
+import gitlab  # pylint: disable=import-error
+
+
+def name_to_path(name):
+    """Converts a name to valid path in GitLab."""
+    return re.sub("[^a-zA-Z0-9_-]", "-", name).lower()
+
+
+def get_url():
+    """Return the GitLab URL."""
+    return os.getenv(
+        "GITLAB_BASE_URL", "https://git.shore.co.il/"
+    ).removesuffix("api/v4")
+
+
+def connect():
+    """Return the GitLab object."""
+    try:
+        token = os.environ["GITLAB_TOKEN"]
+    except KeyError:
+        raise Exception("GITLAB_TOKEN environment variable not set.")
+    url = get_url()
+    conn = gitlab.Gitlab(url=url, private_token=token)
+    conn.auth()
+    return conn
+
+
+def get_group(conn, name):
+    """Return the GitLab group object with the given name."""
+    for group in conn.groups.list(all=True):
+        if group.name == name:
+            return group
+    return None
+
+
+def create_group(conn, name, visibility=None, description=None):
+    """Create a new GitLab group and return that object."""
+    data = {
+        "name": name,
+        "path": name_to_path(name),
+        "visibility": "public" if visibility is None else visibility,
+    }
+    if description is not None:
+        data["description"] = description
+    return conn.groups.create(data)
+
+
+def get_project(conn, group, name):
+    """Returns a GitLab project."""
+    # pylint: disable=invalid-name
+    g = get_group(conn, group)
+    if g is None:
+        return None
+    for p in g.projects.list(all=True):
+        if p.name == name:
+            return p
+    return None
+
+
+def create_project(conn, name, group=None, description=None, visibility=None):
+    """Create a new GitLab project and return that object."""
+    # pylint: disable=invalid-name
+    data = {
+        "name": name,
+    }
+    if group is not None:
+        g = get_group(conn, group)
+        if g is None:
+            return None
+        data["namespace_id"] = g.id
+    if description is not None:
+        data["description"] = description
+    if visibility is not None:
+        data["visibility"] = visibility
+    return conn.projects.create(data)