Skip to content
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
32 changes: 30 additions & 2 deletions src/subcommand/init_subcommand.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// #include <filesystem>
#include <filesystem>
#include "init_subcommand.hpp"
#include "../wrapper/repository_wrapper.hpp"

Expand All @@ -12,11 +12,39 @@ init_subcommand::init_subcommand(const libgit2_object&, CLI::App& app)
sub->add_option("directory", m_directory, "info about directory arg")
->check(CLI::ExistingDirectory | CLI::NonexistentPath)
->default_val(get_current_git_path());
sub->add_option("-b,--initial-branch", m_branch, "Use <branch-name> for the initial branch in the newly created repository. If not specified, fall back to the default name.");

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

void init_subcommand::run()
{
repository_wrapper::init(m_directory, m_bare);
std::filesystem::path target_dir = m_directory;
bool reinit = std::filesystem::exists(target_dir / ".git" / "HEAD");

repository_wrapper repo = [this]() {
if (m_branch.empty())
{
return repository_wrapper::init(m_directory, m_bare);
}
else
{
git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
if (m_bare) { opts.flags |= GIT_REPOSITORY_INIT_BARE; }
opts.initial_head = m_branch.c_str();

return repository_wrapper::init_ext(m_directory, &opts);
}
}();

std::string path = repo.path();

if (reinit)
{
std::cout << "Reinitialized existing Git repository in " << path <<std::endl;
}
else
{
std::cout << "Initialized empty Git repository in " << path <<std::endl;
}
}
1 change: 1 addition & 0 deletions src/subcommand/init_subcommand.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ class init_subcommand
private:
bool m_bare = false;
std::string m_directory;
std::string m_branch;
};
13 changes: 10 additions & 3 deletions src/wrapper/repository_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,21 @@ repository_wrapper repository_wrapper::init(std::string_view directory, bool bar
return rw;
}

repository_wrapper repository_wrapper::init_ext(std::string_view directory, git_repository_init_options* opts)
{
repository_wrapper rw;
throw_if_error(git_repository_init_ext(&(rw.p_resource), directory.data(), opts));
return rw;
}

repository_wrapper repository_wrapper::clone(std::string_view url, std::string_view path, const git_clone_options& opts)
{
repository_wrapper rw;
throw_if_error(git_clone(&(rw.p_resource), url.data(), path.data(), &opts));
return rw;
}

std::string repository_wrapper::git_path() const
std::string repository_wrapper::path() const
{
return git_repository_path(*this);
}
Expand Down Expand Up @@ -343,8 +350,8 @@ size_t repository_wrapper::shallow_depth_from_head() const
return 0u;
}

std::string git_path = this->git_path();
std::string shallow_path = git_path + "shallow";
std::string path = this->path();
std::string shallow_path = path + "shallow";

std::vector<git_oid> boundaries_list;
std::ifstream f(shallow_path);
Expand Down
3 changes: 2 additions & 1 deletion src/wrapper/repository_wrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@ class repository_wrapper : public wrapper_base<git_repository>
repository_wrapper& operator=(repository_wrapper&&) noexcept = default;

static repository_wrapper init(std::string_view directory, bool bare);
static repository_wrapper init_ext(std::string_view repo_path, git_repository_init_options* opts);
static repository_wrapper open(std::string_view directory);
static repository_wrapper clone(std::string_view url, std::string_view path, const git_clone_options& opts);

std::string git_path() const;
std::string path() const;
git_repository_state_t state() const;
void state_cleanup();

Expand Down
6 changes: 3 additions & 3 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def commit_env_config(monkeypatch):

@pytest.fixture
def repo_init_with_commit(commit_env_config, git2cpp_path, tmp_path, run_in_tmp_path):
cmd_init = [git2cpp_path, "init", "."]
cmd_init = [git2cpp_path, "init", ".", "-b", "main"]
p_init = subprocess.run(cmd_init, capture_output=True, cwd=tmp_path, text=True)
assert p_init.returncode == 0

Expand All @@ -70,7 +70,7 @@ def repo_init_with_commit(commit_env_config, git2cpp_path, tmp_path, run_in_tmp_

@pytest.fixture(scope="session")
def private_test_repo():
# Fixture containing everything needed to access private github repo.
# Fixture containing everything needed to access private github repo.
# GIT2CPP_TEST_PRIVATE_TOKEN is the fine-grained Personal Access Token for private test repo.
# If this is not available as an environment variable, tests that use this fixture are skipped.
token = os.getenv("GIT2CPP_TEST_PRIVATE_TOKEN")
Expand All @@ -82,5 +82,5 @@ def private_test_repo():
"repo_name": repo_name,
"http_url": f"http://github.com/{org_name}/{repo_name}",
"https_url": f"https://github.com/{org_name}/{repo_name}",
"token": token
"token": token,
}
42 changes: 9 additions & 33 deletions test/test_checkout.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,6 @@
def test_checkout(repo_init_with_commit, git2cpp_path, tmp_path):
assert (tmp_path / "initial.txt").exists()

default_branch = subprocess.run(
["git", "branch", "--show-current"],
capture_output=True,
cwd=tmp_path,
text=True,
check=True,
).stdout.strip() # TODO: use git2cpp when "branch --show-current" is implemented

create_cmd = [git2cpp_path, "branch", "foregone"]
p_create = subprocess.run(create_cmd, capture_output=True, cwd=tmp_path, text=True)
assert p_create.returncode == 0
Expand All @@ -28,27 +20,19 @@ def test_checkout(repo_init_with_commit, git2cpp_path, tmp_path):
branch_cmd = [git2cpp_path, "branch"]
p_branch = subprocess.run(branch_cmd, capture_output=True, cwd=tmp_path, text=True)
assert p_branch.returncode == 0
assert p_branch.stdout == f"* foregone\n {default_branch}\n"
assert p_branch.stdout == "* foregone\n main\n"

checkout_cmd[2] = default_branch
checkout_cmd[2] = "main"
p_checkout2 = subprocess.run(
checkout_cmd, capture_output=True, cwd=tmp_path, text=True
)
assert p_checkout2.returncode == 0
assert f"Switched to branch '{default_branch}'" in p_checkout2.stdout
assert "Switched to branch 'main'" in p_checkout2.stdout


def test_checkout_b(repo_init_with_commit, git2cpp_path, tmp_path):
assert (tmp_path / "initial.txt").exists()

default_branch = subprocess.run(
["git", "branch", "--show-current"],
capture_output=True,
cwd=tmp_path,
text=True,
check=True,
).stdout.strip() # TODO: use git2cpp when "branch --show-current" is implemented

checkout_cmd = [git2cpp_path, "checkout", "-b", "foregone"]
p_checkout = subprocess.run(
checkout_cmd, capture_output=True, cwd=tmp_path, text=True
Expand All @@ -59,16 +43,16 @@ def test_checkout_b(repo_init_with_commit, git2cpp_path, tmp_path):
branch_cmd = [git2cpp_path, "branch"]
p_branch = subprocess.run(branch_cmd, capture_output=True, cwd=tmp_path, text=True)
assert p_branch.returncode == 0
assert p_branch.stdout == f"* foregone\n {default_branch}\n"
assert p_branch.stdout == "* foregone\n main\n"

checkout_cmd.remove("-b")
checkout_cmd[2] = default_branch
checkout_cmd[2] = "main"
p_checkout2 = subprocess.run(checkout_cmd, cwd=tmp_path, text=True)
assert p_checkout2.returncode == 0

p_branch2 = subprocess.run(branch_cmd, capture_output=True, cwd=tmp_path, text=True)
assert p_branch2.returncode == 0
assert p_branch2.stdout == f" foregone\n* {default_branch}\n"
assert p_branch2.stdout == " foregone\n* main\n"


def test_checkout_B_force_create(repo_init_with_commit, git2cpp_path, tmp_path):
Expand Down Expand Up @@ -143,14 +127,6 @@ def test_checkout_refuses_overwrite(
initial_file = tmp_path / "initial.txt"
assert (initial_file).exists()

default_branch = subprocess.run(
["git", "branch", "--show-current"],
capture_output=True,
cwd=tmp_path,
text=True,
check=True,
).stdout.strip() # TODO: use git2cpp when "branch --show-current" is implemented

# Create a new branch and switch to it
create_cmd = [git2cpp_path, "checkout", "-b", "newbranch"]
p_create = subprocess.run(create_cmd, capture_output=True, cwd=tmp_path, text=True)
Expand All @@ -166,14 +142,14 @@ def test_checkout_refuses_overwrite(
subprocess.run(commit_cmd, cwd=tmp_path, text=True)

# Switch back to default branch
checkout_default_cmd = [git2cpp_path, "checkout", default_branch]
checkout_default_cmd = [git2cpp_path, "checkout", "main"]
p_default = subprocess.run(
checkout_default_cmd, capture_output=True, cwd=tmp_path, text=True
)
assert p_default.returncode == 0

# Now modify initial.txt locally (unstaged) on default branch
initial_file.write_text(f"Local modification on {default_branch}")
initial_file.write_text("Local modification on main")

# Try to checkout newbranch
checkout_cmd = [git2cpp_path, "checkout"]
Expand Down Expand Up @@ -201,7 +177,7 @@ def test_checkout_refuses_overwrite(
p_branch = subprocess.run(
branch_cmd, capture_output=True, cwd=tmp_path, text=True
)
assert f"* {default_branch}" in p_branch.stdout
assert "* main" in p_branch.stdout
else:
assert "Switched to branch 'newbranch'" in p_checkout.stdout

Expand Down
82 changes: 72 additions & 10 deletions test/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ def test_init_in_directory(git2cpp_path, tmp_path):
assert list(tmp_path.iterdir()) == []

cmd = [git2cpp_path, "init", "--bare", str(tmp_path)]
p = subprocess.run(cmd, capture_output=True)
p = subprocess.run(cmd, capture_output=True, text=True)
assert p.returncode == 0
assert p.stdout == b""
assert p.stderr == b""
assert p.stdout.startswith("Initialized empty Git repository in ")
assert p.stdout.strip().endswith("/")

assert sorted(map(lambda path: path.name, tmp_path.iterdir())) == [
"HEAD",
Expand All @@ -31,10 +31,10 @@ def test_init_in_cwd(git2cpp_path, tmp_path, run_in_tmp_path):
assert Path.cwd() == tmp_path

cmd = [git2cpp_path, "init", "--bare"]
p = subprocess.run(cmd, capture_output=True)
p = subprocess.run(cmd, capture_output=True, text=True)
assert p.returncode == 0
assert p.stdout == b""
assert p.stderr == b""
assert p.stdout.startswith("Initialized empty Git repository in ")
assert p.stdout.strip().endswith("/")

assert sorted(map(lambda path: path.name, tmp_path.iterdir())) == [
"HEAD",
Expand All @@ -52,12 +52,12 @@ def test_init_in_cwd(git2cpp_path, tmp_path, run_in_tmp_path):
def test_init_not_bare(git2cpp_path, tmp_path):
# tmp_path exists and is empty.
assert list(tmp_path.iterdir()) == []

assert not (tmp_path / ".git" / "HEAD").exists()
cmd = [git2cpp_path, "init", "."]
p = subprocess.run(cmd, capture_output=True, cwd=tmp_path)
p = subprocess.run(cmd, capture_output=True, cwd=tmp_path, text=True)
assert p.returncode == 0
assert p.stdout == b""
assert p.stderr == b""
assert p.stdout.startswith("Initialized empty Git repository in ")
assert p.stdout.strip().endswith(".git/")

# Directory contains just .git directory.
assert sorted(map(lambda path: path.name, tmp_path.iterdir())) == [".git"]
Expand All @@ -79,6 +79,19 @@ def test_init_not_bare(git2cpp_path, tmp_path):
assert b"does not have any commits yet" in p.stdout


def test_init_reinitializes_existing_repo_message(git2cpp_path, tmp_path):
cmd = [git2cpp_path, "init", "."]

p1 = subprocess.run(cmd, capture_output=True, cwd=tmp_path, text=True)
assert p1.returncode == 0

p2 = subprocess.run(cmd, capture_output=True, cwd=tmp_path, text=True)
assert p2.returncode == 0
assert p2.stderr == ""
assert p2.stdout.startswith("Reinitialized existing Git repository in ")
assert p2.stdout.strip().endswith("/.git/")


def test_error_on_unknown_option(git2cpp_path):
cmd = [git2cpp_path, "init", "--unknown"]
p = subprocess.run(cmd, capture_output=True)
Expand All @@ -93,3 +106,52 @@ def test_error_on_repeated_directory(git2cpp_path):
assert p.returncode == 109
assert p.stdout == b""
assert p.stderr.startswith(b"The following argument was not expected: def")


def test_init_creates_missing_parent_directories(git2cpp_path, tmp_path):
# Parent "does-not-exist" does not exist yet.
repo_dir = tmp_path / "does-not-exist" / "repo"
assert not repo_dir.parent.exists()

cmd = [git2cpp_path, "init", "--bare", str(repo_dir)]
p = subprocess.run(cmd, capture_output=True, text=True)

assert p.returncode == 0
assert p.stdout.startswith("Initialized empty Git repository in ")
assert p.stdout.strip().endswith("/")
assert ".git" not in p.stdout

assert repo_dir.exists()
assert sorted(p.name for p in repo_dir.iterdir()) == [
"HEAD",
"config",
"description",
"hooks",
"info",
"objects",
"refs",
]


def test_init_initial_branch_non_bare(git2cpp_path, tmp_path):
cmd = [git2cpp_path, "init", "-b", "main", "."]
p = subprocess.run(cmd, capture_output=True, cwd=tmp_path, text=True)
assert p.returncode == 0
assert p.stderr == ""
assert p.stdout.startswith("Initialized empty Git repository in ")
assert p.stdout.strip().endswith("/.git/")

head = (tmp_path / ".git" / "HEAD").read_text()
assert "refs/heads/main" in head


def test_init_initial_branch_bare(git2cpp_path, tmp_path):
cmd = [git2cpp_path, "init", "--bare", "-b", "main", str(tmp_path)]
p = subprocess.run(cmd, capture_output=True, text=True)
assert p.returncode == 0
assert p.stderr == ""
assert p.stdout.startswith("Initialized empty Git repository in ")
assert p.stdout.strip().endswith("/")

head = (tmp_path / "HEAD").read_text()
assert "refs/heads/main" in head
Loading
Loading