Posts Of source control and databases - Part 1 - Initializing a repository
Post
Cancel

Of source control and databases - Part 1 - Initializing a repository


All the code for the articles in this series can be found here.


The (obvious) first step in our endeavor is to allow users to set up a repository. To this end, we’ll offer them an init command.

Interface

Before going into the implementation of the init command, we need to know its requirements. If we went blindly into this task, we might be tempted to implement the full git init interface which goes something like (as of git 2.17.1):

1
2
3
git init [-q | --quiet] [--bare] [--template=<template_directory>]
          [--separate-git-dir <git dir>]
          [--shared[=<permissions>]] [directory]

This is a bit much for a programming assignment where we want the focus to be on source control concepts. That is why I asked students to only implement this minimalistic interface for the init command:

1
dvcsus init

What this will do is create an empty repository in the folder from which the command is executed. Nothing more, nothing less.

.dvcs Folder

This brings us the question: what is a DVCSUS repository? The first part of the answer is: a folder. As with the other distributed source control software, there needs to be local components to DVCSUS that’ll contain information about what the user is working on (copy of which distant repository) and what this work entails (new files, file modifications and so on). In turn, these local components need to be stored somewhere and that somewhere is the .dvcs folder.

Thankfully, the standard C++ library provides us with all the tools we need to create this folder:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <fmt/format.h>
#include <fmt/ostream.h>

#include <filesystem>

namespace fs = std::filesystem;

[[nodiscard]] bool CreateDVCSFolder() noexcept
{
  try
  {
    const auto dvcsPath = fs::current_path() / dvcs::DVCS_PATH;
    if (fs::exists(dvcsPath))
    {   
      // We don't allow overwriting an existing repository                                                                                        
      fmt::print(std::cerr, "Repository already initialized in {}", 
                 fs::current_path().string());
      return false;
    }
    return fs::create_directory(dvcsPath);
  }
  catch (const std::exception &e)
  {
    fmt::print(std::cerr, "{}\n", e.what());
    return false;
  }
}

Note that I’ll be using the excellent fmtlib throughout this series for all my formatting needs. I’d use std::format, but, at the time of writing these articles, the support for it is lacking in all major standard library implementations.

Databases

The second part of the answer to what is a DVCSUS repository is: databases.

Yes, databases. Plural form.

Recall how I’ve defined what goes into the .dvcs folder: information about what is being worked on and information about what that work is. This is quite literally how we’ll store data on disk.

First, we’ll have one database that’ll represent the repository as such. This is the data that everyone working on a given repository must have. When a user will push or pull, this is the database that’ll get into play.

Then, we’ll also have one database in which we’ll store everything that’s local to a given user. For example, it’ll contain all of the local modifications that a user would want to commit. It’ll also contain some metadata that’ll help us manage the local component of the distributed version control software.

This segregation makes it clear what should and shouldn’t be made available to everyone working on a given repository.

Putting all of that into place gives us this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#define RETURN_IF(cond, val)    \
    if (cond)                   \
    {                           \
        return val;             \
    }

[[nodiscard]] bool Init() noexcept
{
    RETURN_IF(!CreateDVCSFolder(), false);
    try
    {
        const auto initQuery(
            fmt::format("...", // Omitted for brevity
                       (fs::current_path() / STAGING_DB_PATH).c_str()));
        RETURN_IF(!ExecuteQuery(REPO_DB_PATH, initQuery), false);
    }
    catch (const std::exception &e)
    {
        fmt::print(std::cerr, "{}\n", e.what());
        return false;
    }

    fmt::print(std::cout, "initialized empty repository: {}\n", 
               fs::current_path().c_str());

    return true;
}

Notice that I’ve hidden most of my dealings with a DBMS behind the ExecuteQuery function since they’re irrelevant at this time. I’ve also skipped over the SQL statements used to set up the databases. Don’t worry, we’ll go over the databases’ schema in later articles.

Up next, we’ll look into how we can add files to a repository.

This post is licensed under CC BY 4.0 by the author.