Skip to content

Commit ff79b63

Browse files
committed
Add -b flag to init subcommand
1 parent 372f1ef commit ff79b63

File tree

10 files changed

+157
-241
lines changed

10 files changed

+157
-241
lines changed

src/subcommand/init_subcommand.cpp

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// #include <filesystem>
1+
#include <filesystem>
22
#include "init_subcommand.hpp"
33
#include "../wrapper/repository_wrapper.hpp"
44

@@ -12,11 +12,49 @@ init_subcommand::init_subcommand(const libgit2_object&, CLI::App& app)
1212
sub->add_option("directory", m_directory, "info about directory arg")
1313
->check(CLI::ExistingDirectory | CLI::NonexistentPath)
1414
->default_val(get_current_git_path());
15+
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.");
1516

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

1920
void init_subcommand::run()
2021
{
21-
repository_wrapper::init(m_directory, m_bare);
22+
std::filesystem::path target_dir = m_directory;
23+
bool reinit = std::filesystem::exists(target_dir / ".git" / "HEAD");
24+
25+
std::string git_path;
26+
if (m_branch.empty())
27+
{
28+
auto repo = repository_wrapper::init(m_directory, m_bare);
29+
git_path = repo.git_path();
30+
}
31+
else
32+
{
33+
git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
34+
if (m_bare) {opts.flags |= GIT_REPOSITORY_INIT_BARE;}
35+
opts.initial_head = m_branch.c_str();
36+
37+
auto repo = repository_wrapper::init_ext(m_directory, &opts);
38+
git_path = repo.git_path();
39+
}
40+
41+
std::string path;
42+
if (m_bare)
43+
{
44+
size_t pos = git_path.find(".git/");
45+
path = git_path.substr(0, pos);
46+
}
47+
else
48+
{
49+
path = git_path;
50+
}
51+
52+
if (reinit)
53+
{
54+
std::cout << "Reinitialized existing Git repository in " << path <<std::endl;
55+
}
56+
else
57+
{
58+
std::cout << "Initialized empty Git repository in " << path <<std::endl;
59+
}
2260
}

src/subcommand/init_subcommand.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ class init_subcommand
1616
private:
1717
bool m_bare = false;
1818
std::string m_directory;
19+
std::string m_branch;
1920
};

src/wrapper/repository_wrapper.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include <algorithm>
22
#include <fstream>
3+
#include <git2/repository.h>
34
#include <iostream>
45

56
#include "../utils/git_exception.hpp"
@@ -31,6 +32,13 @@ repository_wrapper repository_wrapper::init(std::string_view directory, bool bar
3132
return rw;
3233
}
3334

35+
repository_wrapper repository_wrapper::init_ext(std::string_view directory, git_repository_init_options* opts)
36+
{
37+
repository_wrapper rw;
38+
throw_if_error(git_repository_init_ext(&(rw.p_resource), directory.data(), opts));
39+
return rw;
40+
}
41+
3442
repository_wrapper repository_wrapper::clone(std::string_view url, std::string_view path, const git_clone_options& opts)
3543
{
3644
repository_wrapper rw;

src/wrapper/repository_wrapper.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class repository_wrapper : public wrapper_base<git_repository>
4040
repository_wrapper& operator=(repository_wrapper&&) noexcept = default;
4141

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

test/conftest.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def commit_env_config(monkeypatch):
5252

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

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

7171
@pytest.fixture(scope="session")
7272
def private_test_repo():
73-
# Fixture containing everything needed to access private github repo.
73+
# Fixture containing everything needed to access private github repo.
7474
# GIT2CPP_TEST_PRIVATE_TOKEN is the fine-grained Personal Access Token for private test repo.
7575
# If this is not available as an environment variable, tests that use this fixture are skipped.
7676
token = os.getenv("GIT2CPP_TEST_PRIVATE_TOKEN")
@@ -82,5 +82,5 @@ def private_test_repo():
8282
"repo_name": repo_name,
8383
"http_url": f"http://github.com/{org_name}/{repo_name}",
8484
"https_url": f"https://github.com/{org_name}/{repo_name}",
85-
"token": token
85+
"token": token,
8686
}

test/test_checkout.py

Lines changed: 9 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,6 @@
66
def test_checkout(repo_init_with_commit, git2cpp_path, tmp_path):
77
assert (tmp_path / "initial.txt").exists()
88

9-
default_branch = subprocess.run(
10-
["git", "branch", "--show-current"],
11-
capture_output=True,
12-
cwd=tmp_path,
13-
text=True,
14-
check=True,
15-
).stdout.strip() # TODO: use git2cpp when "branch --show-current" is implemented
16-
179
create_cmd = [git2cpp_path, "branch", "foregone"]
1810
p_create = subprocess.run(create_cmd, capture_output=True, cwd=tmp_path, text=True)
1911
assert p_create.returncode == 0
@@ -28,27 +20,19 @@ def test_checkout(repo_init_with_commit, git2cpp_path, tmp_path):
2820
branch_cmd = [git2cpp_path, "branch"]
2921
p_branch = subprocess.run(branch_cmd, capture_output=True, cwd=tmp_path, text=True)
3022
assert p_branch.returncode == 0
31-
assert p_branch.stdout == f"* foregone\n {default_branch}\n"
23+
assert p_branch.stdout == "* foregone\n main\n"
3224

33-
checkout_cmd[2] = default_branch
25+
checkout_cmd[2] = "main"
3426
p_checkout2 = subprocess.run(
3527
checkout_cmd, capture_output=True, cwd=tmp_path, text=True
3628
)
3729
assert p_checkout2.returncode == 0
38-
assert f"Switched to branch '{default_branch}'" in p_checkout2.stdout
30+
assert "Switched to branch 'main'" in p_checkout2.stdout
3931

4032

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

44-
default_branch = subprocess.run(
45-
["git", "branch", "--show-current"],
46-
capture_output=True,
47-
cwd=tmp_path,
48-
text=True,
49-
check=True,
50-
).stdout.strip() # TODO: use git2cpp when "branch --show-current" is implemented
51-
5236
checkout_cmd = [git2cpp_path, "checkout", "-b", "foregone"]
5337
p_checkout = subprocess.run(
5438
checkout_cmd, capture_output=True, cwd=tmp_path, text=True
@@ -59,16 +43,16 @@ def test_checkout_b(repo_init_with_commit, git2cpp_path, tmp_path):
5943
branch_cmd = [git2cpp_path, "branch"]
6044
p_branch = subprocess.run(branch_cmd, capture_output=True, cwd=tmp_path, text=True)
6145
assert p_branch.returncode == 0
62-
assert p_branch.stdout == f"* foregone\n {default_branch}\n"
46+
assert p_branch.stdout == "* foregone\n main\n"
6347

6448
checkout_cmd.remove("-b")
65-
checkout_cmd[2] = default_branch
49+
checkout_cmd[2] = "main"
6650
p_checkout2 = subprocess.run(checkout_cmd, cwd=tmp_path, text=True)
6751
assert p_checkout2.returncode == 0
6852

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

7357

7458
def test_checkout_B_force_create(repo_init_with_commit, git2cpp_path, tmp_path):
@@ -143,14 +127,6 @@ def test_checkout_refuses_overwrite(
143127
initial_file = tmp_path / "initial.txt"
144128
assert (initial_file).exists()
145129

146-
default_branch = subprocess.run(
147-
["git", "branch", "--show-current"],
148-
capture_output=True,
149-
cwd=tmp_path,
150-
text=True,
151-
check=True,
152-
).stdout.strip() # TODO: use git2cpp when "branch --show-current" is implemented
153-
154130
# Create a new branch and switch to it
155131
create_cmd = [git2cpp_path, "checkout", "-b", "newbranch"]
156132
p_create = subprocess.run(create_cmd, capture_output=True, cwd=tmp_path, text=True)
@@ -166,14 +142,14 @@ def test_checkout_refuses_overwrite(
166142
subprocess.run(commit_cmd, cwd=tmp_path, text=True)
167143

168144
# Switch back to default branch
169-
checkout_default_cmd = [git2cpp_path, "checkout", default_branch]
145+
checkout_default_cmd = [git2cpp_path, "checkout", "main"]
170146
p_default = subprocess.run(
171147
checkout_default_cmd, capture_output=True, cwd=tmp_path, text=True
172148
)
173149
assert p_default.returncode == 0
174150

175151
# Now modify initial.txt locally (unstaged) on default branch
176-
initial_file.write_text(f"Local modification on {default_branch}")
152+
initial_file.write_text("Local modification on main")
177153

178154
# Try to checkout newbranch
179155
checkout_cmd = [git2cpp_path, "checkout"]
@@ -201,7 +177,7 @@ def test_checkout_refuses_overwrite(
201177
p_branch = subprocess.run(
202178
branch_cmd, capture_output=True, cwd=tmp_path, text=True
203179
)
204-
assert f"* {default_branch}" in p_branch.stdout
180+
assert "* main" in p_branch.stdout
205181
else:
206182
assert "Switched to branch 'newbranch'" in p_checkout.stdout
207183

test/test_init.py

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ def test_init_in_directory(git2cpp_path, tmp_path):
77
assert list(tmp_path.iterdir()) == []
88

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

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

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

3939
assert sorted(map(lambda path: path.name, tmp_path.iterdir())) == [
4040
"HEAD",
@@ -52,12 +52,12 @@ def test_init_in_cwd(git2cpp_path, tmp_path, run_in_tmp_path):
5252
def test_init_not_bare(git2cpp_path, tmp_path):
5353
# tmp_path exists and is empty.
5454
assert list(tmp_path.iterdir()) == []
55-
55+
assert not (tmp_path / ".git" / "HEAD").exists()
5656
cmd = [git2cpp_path, "init", "."]
57-
p = subprocess.run(cmd, capture_output=True, cwd=tmp_path)
57+
p = subprocess.run(cmd, capture_output=True, cwd=tmp_path, text=True)
5858
assert p.returncode == 0
59-
assert p.stdout == b""
60-
assert p.stderr == b""
59+
assert p.stdout.startswith("Initialized empty Git repository in ")
60+
assert p.stdout.strip().endswith(".git/")
6161

6262
# Directory contains just .git directory.
6363
assert sorted(map(lambda path: path.name, tmp_path.iterdir())) == [".git"]
@@ -93,3 +93,52 @@ def test_error_on_repeated_directory(git2cpp_path):
9393
assert p.returncode == 109
9494
assert p.stdout == b""
9595
assert p.stderr.startswith(b"The following argument was not expected: def")
96+
97+
98+
def test_init_creates_missing_parent_directories(git2cpp_path, tmp_path):
99+
# Parent "does-not-exist" does not exist yet.
100+
repo_dir = tmp_path / "does-not-exist" / "repo"
101+
assert not repo_dir.parent.exists()
102+
103+
cmd = [git2cpp_path, "init", "--bare", str(repo_dir)]
104+
p = subprocess.run(cmd, capture_output=True, text=True)
105+
106+
assert p.returncode == 0
107+
assert p.stdout.startswith("Initialized empty Git repository in ")
108+
assert p.stdout.strip().endswith("/")
109+
assert ".git" not in p.stdout
110+
111+
assert repo_dir.exists()
112+
assert sorted(p.name for p in repo_dir.iterdir()) == [
113+
"HEAD",
114+
"config",
115+
"description",
116+
"hooks",
117+
"info",
118+
"objects",
119+
"refs",
120+
]
121+
122+
123+
def test_init_initial_branch_non_bare(git2cpp_path, tmp_path):
124+
cmd = [git2cpp_path, "init", "-b", "main", "."]
125+
p = subprocess.run(cmd, capture_output=True, cwd=tmp_path, text=True)
126+
assert p.returncode == 0
127+
assert p.stderr == ""
128+
assert p.stdout.startswith("Initialized empty Git repository in ")
129+
assert p.stdout.strip().endswith("/.git/")
130+
131+
head = (tmp_path / ".git" / "HEAD").read_text()
132+
assert "refs/heads/main" in head
133+
134+
135+
def test_init_initial_branch_bare(git2cpp_path, tmp_path):
136+
cmd = [git2cpp_path, "init", "--bare", "-b", "main", str(tmp_path)]
137+
p = subprocess.run(cmd, capture_output=True, text=True)
138+
assert p.returncode == 0
139+
assert p.stderr == ""
140+
assert p.stdout.startswith("Initialized empty Git repository in ")
141+
assert p.stdout.strip().endswith("/")
142+
143+
head = (tmp_path / "HEAD").read_text()
144+
assert "refs/heads/main" in head

test/test_merge.py

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,6 @@ def test_merge_fast_forward(
1010
):
1111
assert (tmp_path / "initial.txt").exists()
1212

13-
default_branch = subprocess.run(
14-
["git", "branch", "--show-current"],
15-
capture_output=True,
16-
cwd=tmp_path,
17-
text=True,
18-
check=True,
19-
).stdout.strip() # TODO: use git2cpp when "branch --show-current" is implemented
20-
2113
checkout_cmd = [git2cpp_path, "checkout", "-b", "foregone"]
2214
p_checkout = subprocess.run(
2315
checkout_cmd, capture_output=True, cwd=tmp_path, text=True
@@ -35,7 +27,7 @@ def test_merge_fast_forward(
3527
p_commit = subprocess.run(commit_cmd, capture_output=True, cwd=tmp_path, text=True)
3628
assert p_commit.returncode == 0
3729

38-
checkout_cmd_2 = [git2cpp_path, "checkout", default_branch]
30+
checkout_cmd_2 = [git2cpp_path, "checkout", "main"]
3931
p_checkout_2 = subprocess.run(
4032
checkout_cmd_2, capture_output=True, cwd=tmp_path, text=True
4133
)
@@ -64,14 +56,6 @@ def test_merge_fast_forward(
6456
def test_merge_commit(repo_init_with_commit, commit_env_config, git2cpp_path, tmp_path):
6557
assert (tmp_path / "initial.txt").exists()
6658

67-
default_branch = subprocess.run(
68-
["git", "branch", "--show-current"],
69-
capture_output=True,
70-
cwd=tmp_path,
71-
text=True,
72-
check=True,
73-
).stdout.strip() # TODO: use git2cpp when "branch --show-current" is implemented
74-
7559
checkout_cmd = [git2cpp_path, "checkout", "-b", "foregone"]
7660
p_checkout = subprocess.run(
7761
checkout_cmd, capture_output=True, cwd=tmp_path, text=True
@@ -89,7 +73,7 @@ def test_merge_commit(repo_init_with_commit, commit_env_config, git2cpp_path, tm
8973
p_commit = subprocess.run(commit_cmd, capture_output=True, cwd=tmp_path, text=True)
9074
assert p_commit.returncode == 0
9175

92-
checkout_cmd_2 = [git2cpp_path, "checkout", default_branch]
76+
checkout_cmd_2 = [git2cpp_path, "checkout", "main"]
9377
p_checkout_2 = subprocess.run(
9478
checkout_cmd_2, capture_output=True, cwd=tmp_path, text=True
9579
)
@@ -135,14 +119,6 @@ def test_merge_conflict(
135119
):
136120
assert (tmp_path / "initial.txt").exists()
137121

138-
default_branch = subprocess.run(
139-
["git", "branch", "--show-current"],
140-
capture_output=True,
141-
cwd=tmp_path,
142-
text=True,
143-
check=True,
144-
).stdout.strip() # TODO: use git2cpp when "branch --show-current" is implemented
145-
146122
checkout_cmd = [git2cpp_path, "checkout", "-b", "foregone"]
147123
p_checkout = subprocess.run(
148124
checkout_cmd, capture_output=True, cwd=tmp_path, text=True
@@ -163,7 +139,7 @@ def test_merge_conflict(
163139
p_commit = subprocess.run(commit_cmd, capture_output=True, cwd=tmp_path, text=True)
164140
assert p_commit.returncode == 0
165141

166-
checkout_cmd_2 = [git2cpp_path, "checkout", default_branch]
142+
checkout_cmd_2 = [git2cpp_path, "checkout", "main"]
167143
p_checkout_2 = subprocess.run(
168144
checkout_cmd_2, capture_output=True, cwd=tmp_path, text=True
169145
)

0 commit comments

Comments
 (0)