Skip to content

feat: add add subcommand #16

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jul 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
build/
__pycache__/
.cache/
compile_commands.json
5 changes: 4 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ find_package(libgit2)
# =====

set(GIT2CPP_SRC
${GIT2CPP_SOURCE_DIR}/subcommand/add_subcommand.cpp
${GIT2CPP_SOURCE_DIR}/subcommand/add_subcommand.hpp
${GIT2CPP_SOURCE_DIR}/subcommand/init_subcommand.cpp
${GIT2CPP_SOURCE_DIR}/subcommand/init_subcommand.hpp
${GIT2CPP_SOURCE_DIR}/subcommand/status_subcommand.cpp
Expand All @@ -47,6 +49,8 @@ set(GIT2CPP_SRC
${GIT2CPP_SOURCE_DIR}/utils/common.hpp
${GIT2CPP_SOURCE_DIR}/utils/git_exception.cpp
${GIT2CPP_SOURCE_DIR}/utils/git_exception.hpp
${GIT2CPP_SOURCE_DIR}/wrapper/index_wrapper.cpp
${GIT2CPP_SOURCE_DIR}/wrapper/index_wrapper.hpp
${GIT2CPP_SOURCE_DIR}/wrapper/refs_wrapper.cpp
${GIT2CPP_SOURCE_DIR}/wrapper/refs_wrapper.hpp
${GIT2CPP_SOURCE_DIR}/wrapper/repository_wrapper.cpp
Expand All @@ -59,4 +63,3 @@ set(GIT2CPP_SRC

add_executable(git2cpp ${GIT2CPP_SRC})
target_link_libraries(git2cpp PRIVATE libgit2::libgit2package)

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Developer's workflow using `micromamba` to manage the dependencies:
```bash
micromamba create -f dev-environment.yml
micromamba activate git2cpp-dev
cmake -Bbuild $CMAKE_INSALL_PREFIX=$CONDA_PREFIX
cmake -Bbuild -DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX
cd build
make -j8
```
Expand Down
2 changes: 1 addition & 1 deletion dev-environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ channels:
dependencies:
- cli11
- libgit2
- meson
- cmake
- pkg-config
- python
- pytest
Expand Down
2 changes: 2 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "utils/git_exception.hpp"
#include "version.hpp"
#include "subcommand/add_subcommand.hpp"
#include "subcommand/init_subcommand.hpp"
#include "subcommand/status_subcommand.hpp"

Expand All @@ -21,6 +22,7 @@ int main(int argc, char** argv)
// Sub commands
init_subcommand init(lg2_obj, app);
status_subcommand status(lg2_obj, app);
add_subcommand add(lg2_obj, app);

app.parse(argc, argv);

Expand Down
39 changes: 39 additions & 0 deletions src/subcommand/add_subcommand.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#include <git2.h>

#include "add_subcommand.hpp"
#include "../wrapper/index_wrapper.hpp"
#include "../wrapper/repository_wrapper.hpp"


add_subcommand::add_subcommand(const libgit2_object&, CLI::App& app)
{
auto *sub = app.add_subcommand("add", "Add file contents to the index");

sub->add_option("files", m_add_files, "Files to add");

sub->add_flag("-A,--all,--no-ignore-removal", m_all_flag, "");
// sub->add_flag("-n,--dryrun", dryrun_flag, "");
// sub->add_flag("-u,--update", update_flag, "");
// sub->add_flag("-v,--verbose", verbose_flag, "");

sub->callback([this]() { this->run(); });
};


void add_subcommand::run()
{
auto directory = get_current_git_path();
auto bare = false;
auto repo = repository_wrapper::init(directory, bare);

index_wrapper index = repo.make_index();

if (m_all_flag)
{
index.add_all();
}
else
{
index.add_entries(m_add_files);
}
}
17 changes: 17 additions & 0 deletions src/subcommand/add_subcommand.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma once

#include <CLI/CLI.hpp>

#include "../utils/common.hpp"

class add_subcommand
{
public:

explicit add_subcommand(const libgit2_object&, CLI::App& app);
void run();

private:
bool m_all_flag = false;
std::vector<std::string> m_add_files;
};
6 changes: 3 additions & 3 deletions src/subcommand/init_subcommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ init_subcommand::init_subcommand(const libgit2_object&, CLI::App& app)
{
auto *sub = app.add_subcommand("init", "Explanation of init here");

sub->add_flag("--bare", bare, "info about bare arg");
sub->add_flag("--bare", m_bare, "info about bare arg");

// If directory not specified, uses cwd.
sub->add_option("directory", directory, "info about directory arg")
sub->add_option("directory", m_directory, "info about directory arg")
->check(CLI::ExistingDirectory | CLI::NonexistentPath)
->default_val(get_current_git_path());

Expand All @@ -18,5 +18,5 @@ init_subcommand::init_subcommand(const libgit2_object&, CLI::App& app)

void init_subcommand::run()
{
repository_wrapper::init(directory, bare);
repository_wrapper::init(m_directory, m_bare);
}
4 changes: 2 additions & 2 deletions src/subcommand/init_subcommand.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ class init_subcommand
void run();

private:
bool bare;
std::string directory;
bool m_bare;
std::string m_directory;
};
19 changes: 7 additions & 12 deletions src/subcommand/status_subcommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,14 @@
status_subcommand::status_subcommand(const libgit2_object&, CLI::App& app)
{
auto *sub = app.add_subcommand("status", "Show modified files in working directory, staged for your next commit");
// Displays paths that have differences between the index file and the current HEAD commit,
// paths that have differences between the working tree and the index file, and paths in the
// working tree that are not tracked by Git (and are not ignored by gitignore[5]).
// The first are what you would commit by running git commit;
// the second and third are what you could commit by running git add before running git commit.

sub->add_flag("-s,--short", short_flag, "Give the output in the short-format.");
sub->add_flag("--long", long_flag, "Give the output in the long-format. This is the default.");
sub->add_flag("-s,--short", m_short_flag, "Give the output in the short-format.");
sub->add_flag("--long", m_long_flag, "Give the output in the long-format. This is the default.");
// sub->add_flag("--porcelain[=<version>]", porcelain, "Give the output in an easy-to-parse format for scripts.
// This is similar to the short output, but will remain stable across Git versions and regardless of user configuration.
// See below for details. The version parameter is used to specify the format version. This is optional and defaults
// to the original version v1 format.");
sub->add_flag("-b,--branch", branch_flag, "Show the branch and tracking info even in short-format.");
sub->add_flag("-b,--branch", m_branch_flag, "Show the branch and tracking info even in short-format.");

sub->callback([this]() { this->run(); });
};
Expand Down Expand Up @@ -152,7 +147,7 @@ void print_entries(std::vector<print_entry> entries_to_print)
}
}

void print_not_tracked(std::vector<print_entry> entries_to_print, const std::set<std::string>& tracked_dir_set,
void print_not_tracked(const std::vector<print_entry>& entries_to_print, const std::set<std::string>& tracked_dir_set,
std::set<std::string>& untracked_dir_set)
{
std::vector<print_entry> not_tracked_entries_to_print{};
Expand Down Expand Up @@ -199,11 +194,11 @@ void status_subcommand::run()
std::vector<std::string> ignored_to_print{};

output_format of = output_format::DEFAULT;
if (short_flag)
if (m_short_flag)
{
of = output_format::SHORT;
}
if (long_flag)
if (m_long_flag)
{
of = output_format::LONG;
}
Expand All @@ -220,7 +215,7 @@ void status_subcommand::run()
}
else
{
if (branch_flag)
if (m_branch_flag)
{
std::cout << "## " << branch_name << std::endl;
}
Expand Down
6 changes: 3 additions & 3 deletions src/subcommand/status_subcommand.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class status_subcommand
void run();

private:
bool branch_flag = false;
bool long_flag = false;
bool short_flag = false;
bool m_branch_flag = false;
bool m_long_flag = false;
bool m_short_flag = false;
};
2 changes: 1 addition & 1 deletion src/utils/common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class wrapper_base
wrapper_base& operator=(wrapper_base&& rhs)
{
std::swap(p_resource, rhs.p_resource);
return this;
return *this;
}

operator resource_type*() const noexcept
Expand Down
4 changes: 0 additions & 4 deletions src/utils/meson.build

This file was deleted.

2 changes: 1 addition & 1 deletion src/version.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#define GIT2CPP_VERSION_MAJOR 0
#define GIT2CPP_VERSION_MINOR 0
#define GIT2CPP_VERSION_PATCH 1
#define GIT2CPP_VERSION_PATCH 2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why updating the version?

Copy link
Collaborator Author

@SandrineP SandrineP Jul 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 0.0.2 has been released but we forgot to change it there, so it's to catch up.


// e.g. ".rc0"
#define GIT2CPP_VERSION_SUFFIX
Expand Down
35 changes: 35 additions & 0 deletions src/wrapper/index_wrapper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include "index_wrapper.hpp"
#include "../utils/git_exception.hpp"
#include "../wrapper/repository_wrapper.hpp"

#include <vector>

index_wrapper::~index_wrapper()
{
git_index_free(p_resource);
p_resource=nullptr;
}

index_wrapper index_wrapper::init(repository_wrapper& rw)
{
index_wrapper index;
throwIfError(git_repository_index(&(index.p_resource), rw));
return index;
}

void index_wrapper::add_entries(std::vector<std::string> patterns)
{
add_impl(std::move(patterns));
}

void index_wrapper::add_all()
{
add_impl({{"."}});
}

void index_wrapper::add_impl(std::vector<std::string> patterns)
{
git_strarray_wrapper array{patterns};
throwIfError(git_index_add_all(*this, array, 0, NULL, NULL));
throwIfError(git_index_write(*this));
}
31 changes: 31 additions & 0 deletions src/wrapper/index_wrapper.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#pragma once

#include <string>
#include <vector>

#include <git2.h>

#include "../utils/common.hpp"

class repository_wrapper;

class index_wrapper : public wrapper_base<git_index>
{
public:

~index_wrapper();

index_wrapper(index_wrapper&&) = default;
index_wrapper& operator=(index_wrapper&&) = default;

static index_wrapper init(repository_wrapper& rw);

void add_entries(std::vector<std::string> patterns);
void add_all();


private:

index_wrapper() = default;
void add_impl(std::vector<std::string> patterns);
};
9 changes: 7 additions & 2 deletions src/wrapper/repository_wrapper.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#include "../utils/git_exception.hpp"
#include "repository_wrapper.hpp"

#include "../utils/git_exception.hpp"

repository_wrapper::~repository_wrapper()
{
Expand All @@ -21,3 +20,9 @@ repository_wrapper repository_wrapper::init(const std::string& directory, bool b
throwIfError(git_repository_init(&(rw.p_resource), directory.c_str(), bare));
return rw;
}

index_wrapper repository_wrapper::make_index()
{
index_wrapper index = index_wrapper::init(*this);
return index;
}
2 changes: 2 additions & 0 deletions src/wrapper/repository_wrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <git2.h>

#include "../utils/common.hpp"
#include "../wrapper/index_wrapper.hpp"

class repository_wrapper : public wrapper_base<git_repository>
{
Expand All @@ -17,6 +18,7 @@ class repository_wrapper : public wrapper_base<git_repository>

static repository_wrapper init(const std::string& directory, bool bare);
static repository_wrapper open(const std::string& directory);
index_wrapper make_index();

private:

Expand Down
Empty file modified test/data/status_data/embeded_git/config
100644 → 100755
Empty file.
36 changes: 36 additions & 0 deletions test/test_add.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import os
import subprocess

import pytest


@pytest.mark.parametrize("all_flag", ["", "-A", "--all", "--no-ignore-removal"])
def test_add(git2cpp_path, all_flag):
with open("./test/mook_file.txt", "x") as f:
pass

with open("./test/mook_file_2.txt", "x") as f:
pass

cmd_add = [git2cpp_path, 'add']
if all_flag != "":
cmd_add.append(all_flag)
else:
cmd_add.append("test/mook_file.txt")
subprocess.run(cmd_add, capture_output=True, text=True)

cmd_status = [git2cpp_path, 'status', "--long"]
p_status = subprocess.run(cmd_status, capture_output=True, text=True)

assert "Changes to be committed" in p_status.stdout
assert "new file" in p_status.stdout
if all_flag != "":
assert "Untracked files" not in p_status.stdout
else:
assert "Untracked files" in p_status.stdout

os.remove("./test/mook_file.txt")
os.remove("./test/mook_file_2.txt")

# undo the add, to leave the test directory at the end the same as it was at the start
subprocess.run(cmd_add, capture_output=True, text=True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this line just to undo the add, to leave the test directory at the end the same as it was at the start? If so can you add a comment saying this, otherwise it will look suspicious in future to have this line without any following assert.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's the reason why ! I'll write a comment.