Skip to content

Commit 57737c2

Browse files
committed
implement git-init
1 parent 5cd746b commit 57737c2

File tree

7 files changed

+213
-18
lines changed

7 files changed

+213
-18
lines changed

Diff for: Cargo.toml

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
[package]
22
name = "grit-cli"
33
version = "1.0.0"
4-
authors = ["Sebastian Thiel <sthiel@thoughtworks.com>"]
4+
authors = ["Sebastian Thiel <byronimo@gmail.com>"]
55
publish = false
66

77
[dependencies]
88
failure = "0.1.1"
99
failure-tools = "4.0.2"
10+
clap = "2.31.2"
11+
12+
[dependencies.git-core]
13+
path = "lib/git-core"
14+
version = "0.1.0"
1015

1116
[[bin]]
1217
name="grit"
@@ -15,3 +20,5 @@ path="src/main.rs"
1520
[profile.release]
1621
panic = 'unwind'
1722
incremental = false
23+
24+
[workspace]

Diff for: README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ The CLI uses various libraries to implement
1313

1414
* **git-core**
1515
* **Repository**
16-
* [ ] initialize
16+
* [x] initialize
1717
* [ ] references
1818
* [ ] index
1919
* [ ] odb

Diff for: lib/git-core/Cargo.toml

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
name = "git-core"
3+
version = "0.1.0"
4+
authors = ["Sebastian Thiel <[email protected]>"]
5+
publish = false
6+
7+
[dependencies]
8+
failure = "0.1.1"

Diff for: lib/git-core/src/lib.rs

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
#[macro_use]
2+
extern crate failure;
3+
4+
use failure::{Error, ResultExt};
5+
use std::{fs::{create_dir, OpenOptions}, io::Write, path::Path, path::PathBuf};
6+
7+
const GIT_DIR_NAME: &'static str = ".git";
8+
9+
const TPL_INFO_EXCLUDE: &'static [u8] =
10+
include_bytes!("../../../tests/snapshots/cli/baseline-init/info/exclude");
11+
const TPL_HOOKS_APPLYPATCH_MSG: &'static [u8] =
12+
include_bytes!("../../../tests/snapshots/cli/baseline-init/hooks/applypatch-msg.sample");
13+
const TPL_HOOKS_COMMIT_MSG: &'static [u8] =
14+
include_bytes!("../../../tests/snapshots/cli/baseline-init/hooks/commit-msg.sample");
15+
const TPL_HOOKS_FSMONITOR_WATCHMAN: &'static [u8] =
16+
include_bytes!("../../../tests/snapshots/cli/baseline-init/hooks/fsmonitor-watchman.sample");
17+
const TPL_HOOKS_POST_UPDATE: &'static [u8] =
18+
include_bytes!("../../../tests/snapshots/cli/baseline-init/hooks/post-update.sample");
19+
const TPL_HOOKS_PRE_APPLYPATCH: &'static [u8] =
20+
include_bytes!("../../../tests/snapshots/cli/baseline-init/hooks/pre-applypatch.sample");
21+
const TPL_HOOKS_PRE_COMMIT: &'static [u8] =
22+
include_bytes!("../../../tests/snapshots/cli/baseline-init/hooks/pre-commit.sample");
23+
const TPL_HOOKS_PRE_PUSH: &'static [u8] =
24+
include_bytes!("../../../tests/snapshots/cli/baseline-init/hooks/pre-push.sample");
25+
const TPL_HOOKS_PRE_REBASE: &'static [u8] =
26+
include_bytes!("../../../tests/snapshots/cli/baseline-init/hooks/pre-rebase.sample");
27+
const TPL_HOOKS_PRE_RECEIVE: &'static [u8] =
28+
include_bytes!("../../../tests/snapshots/cli/baseline-init/hooks/pre-receive.sample");
29+
const TPL_HOOKS_PREPARE_COMMIT_MSG: &'static [u8] =
30+
include_bytes!("../../../tests/snapshots/cli/baseline-init/hooks/prepare-commit-msg.sample");
31+
const TPL_HOOKS_UPDATE: &'static [u8] =
32+
include_bytes!("../../../tests/snapshots/cli/baseline-init/hooks/update.sample");
33+
const TPL_CONFIG: &'static [u8] =
34+
include_bytes!("../../../tests/snapshots/cli/baseline-init/config");
35+
const TPL_DESCRIPTION: &'static [u8] =
36+
include_bytes!("../../../tests/snapshots/cli/baseline-init/description");
37+
const TPL_HEAD: &'static [u8] = include_bytes!("../../../tests/snapshots/cli/baseline-init/HEAD");
38+
39+
struct PathCursor<'a>(&'a mut PathBuf);
40+
struct NewDir<'a>(&'a mut PathBuf);
41+
42+
impl<'a> PathCursor<'a> {
43+
fn at(&mut self, component: &str) -> &Path {
44+
self.0.push(component);
45+
self.0.as_path()
46+
}
47+
}
48+
49+
impl<'a> NewDir<'a> {
50+
fn at(self, component: &str) -> Result<Self, Error> {
51+
self.0.push(component);
52+
create_dir(&self.0)?;
53+
Ok(self)
54+
}
55+
fn as_mut(&mut self) -> &mut PathBuf {
56+
self.0
57+
}
58+
}
59+
60+
impl<'a> Drop for NewDir<'a> {
61+
fn drop(&mut self) {
62+
self.0.pop();
63+
}
64+
}
65+
66+
impl<'a> Drop for PathCursor<'a> {
67+
fn drop(&mut self) {
68+
self.0.pop();
69+
}
70+
}
71+
72+
fn write_file(data: &[u8], path: &Path) -> Result<(), Error> {
73+
let mut file = OpenOptions::new()
74+
.write(true)
75+
.create(true)
76+
.append(false)
77+
.open(path)?;
78+
file.write_all(data)
79+
.with_context(|_| format!("Could not initialize file at '{}'", path.display()))
80+
.map_err(Into::into)
81+
}
82+
83+
pub fn init() -> Result<(), Error> {
84+
let mut cursor = PathBuf::from(GIT_DIR_NAME);
85+
if cursor.is_dir() {
86+
bail!(
87+
"Refusing to initialize the existing '{}' directory",
88+
cursor.display()
89+
)
90+
}
91+
create_dir(&cursor)?;
92+
93+
{
94+
let mut cursor = NewDir(&mut cursor).at("info")?;
95+
write_file(TPL_INFO_EXCLUDE, PathCursor(cursor.as_mut()).at("exclude"))?;
96+
}
97+
98+
{
99+
let mut cursor = NewDir(&mut cursor).at("hooks")?;
100+
for (tpl, filename) in &[
101+
(TPL_HOOKS_UPDATE, "update.sample"),
102+
(TPL_HOOKS_PREPARE_COMMIT_MSG, "prepare-commit-msg.sample"),
103+
(TPL_HOOKS_PRE_RECEIVE, "pre-receive.sample"),
104+
(TPL_HOOKS_PRE_REBASE, "pre-rebase.sample"),
105+
(TPL_HOOKS_PRE_PUSH, "pre-push.sample"),
106+
(TPL_HOOKS_PRE_COMMIT, "pre-commit.sample"),
107+
(TPL_HOOKS_PRE_APPLYPATCH, "pre-applypatch.sample"),
108+
(TPL_HOOKS_POST_UPDATE, "post-update.sample"),
109+
(TPL_HOOKS_FSMONITOR_WATCHMAN, "fsmonitor-watchman.sample"),
110+
(TPL_HOOKS_COMMIT_MSG, "commit-msg.sample"),
111+
(TPL_HOOKS_APPLYPATCH_MSG, "applypatch-msg.sample"),
112+
] {
113+
write_file(tpl, PathCursor(cursor.as_mut()).at(filename))?;
114+
}
115+
}
116+
117+
{
118+
let mut cursor = NewDir(&mut cursor).at("objects")?;
119+
create_dir(PathCursor(cursor.as_mut()).at("info"))?;
120+
create_dir(PathCursor(cursor.as_mut()).at("pack"))?;
121+
}
122+
123+
{
124+
let mut cursor = NewDir(&mut cursor).at("refs")?;
125+
create_dir(PathCursor(cursor.as_mut()).at("heads"))?;
126+
create_dir(PathCursor(cursor.as_mut()).at("tags"))?;
127+
}
128+
129+
for (tpl, filename) in &[
130+
(TPL_HEAD, "HEAD"),
131+
(TPL_DESCRIPTION, "description"),
132+
(TPL_CONFIG, "config"),
133+
] {
134+
write_file(tpl, PathCursor(&mut cursor).at(filename))?;
135+
}
136+
137+
Ok(())
138+
}

Diff for: src/main.rs

+31-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,34 @@
11
extern crate failure;
22
extern crate failure_tools;
3+
#[macro_use]
4+
extern crate clap;
5+
extern crate git_core as git;
36

4-
fn main() {}
7+
use failure_tools::ok_or_exit;
8+
use failure::{Error, ResultExt};
9+
10+
mod app {
11+
use clap::{App, AppSettings, SubCommand};
12+
13+
pub fn new<'a, 'b>() -> App<'a, 'b> {
14+
let app: App = app_from_crate!();
15+
app.setting(AppSettings::SubcommandRequired).subcommand(
16+
SubCommand::with_name("init")
17+
.alias("initialize")
18+
.about("Initialize the repository in the current directory."),
19+
)
20+
}
21+
}
22+
23+
fn run() -> Result<(), Error> {
24+
let app = app::new();
25+
let matches = app.get_matches();
26+
match matches.subcommand() {
27+
("init", Some(_args)) => git::init().with_context(|_| "Repository initialization failed"),
28+
_ => unreachable!(),
29+
}.map_err(Into::into)
30+
}
31+
32+
fn main() {
33+
ok_or_exit(run())
34+
}

Diff for: tests/snapshots/cli/init-fail

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
error: Repository initialization failed
2+
Caused by:
3+
1: Refusing to initialize the existing '.git' directory

Diff for: tests/stateless-journey.sh

+24-15
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,37 @@ exe="${root}/../$exe"
99
# shellcheck disable=1090
1010
source "$root/utilities.sh"
1111
snapshot="$root/snapshots/cli"
12-
fixture="$root/fixtures"
12+
# fixture="$root/fixtures"
1313

1414
SUCCESSFULLY=0
1515
WITH_FAILURE=1
1616

1717
title "CLI"
1818
(when "initializing a repository"
19-
(sandbox
20-
precondition "an initialized baseline repository" && {
21-
git init &>/dev/null
22-
expect_snapshot "$snapshot/baseline-init" .git
23-
}
24-
)
25-
(sandbox
26-
it "succeeds" && {
27-
WITH_SNAPSHOT="$snapshot/init-success" \
28-
expect_run $SUCCESSFULLY "$exe" init
29-
}
19+
(with "an empty directory"
20+
(sandbox
21+
precondition "an initialized baseline repository" && {
22+
git init &>/dev/null
23+
expect_snapshot "$snapshot/baseline-init" .git
24+
}
25+
)
26+
(sandbox
27+
it "succeeds" && {
28+
WITH_SNAPSHOT="$snapshot/init-success" \
29+
expect_run $SUCCESSFULLY "$exe" init
30+
}
3031

31-
it "matches the output of baseline git init" && {
32-
expect_snapshot "$snapshot/baseline-init" .git
33-
}
32+
it "matches the output of baseline git init" && {
33+
expect_snapshot "$snapshot/baseline-init" .git
34+
}
35+
36+
(when "trying to initialize the same directory again"
37+
it "fails" && {
38+
WITH_SNAPSHOT="$snapshot/init-fail" \
39+
expect_run $WITH_FAILURE "$exe" init
40+
}
41+
)
42+
)
3443
)
3544
)
3645

0 commit comments

Comments
 (0)