Skip to content

Commit 3d7575e

Browse files
Feat/implement tag (#54)
* Implement tag * Use a singleton instance to manage parsing * Write tests for tag command * Add test cases * change repo when needed * Fix tests and make singleton pattern better
1 parent 19484bd commit 3d7575e

File tree

18 files changed

+322
-102
lines changed

18 files changed

+322
-102
lines changed

.clang-format

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
InsertNewlineAtEOF: true
1+
InsertNewlineAtEOF: true

CMakeLists.txt

Lines changed: 49 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,51 @@
11
cmake_minimum_required(VERSION 3.8.2)
22

3-
#Set a name and a version number for your project:
4-
project(cpp - project - template VERSION 0.0.1 LANGUAGES CXX)
5-
6-
set(CMAKE_AR / usr / bin / ar)
7-
8-
#Enable C language
9-
enable_language(C)
10-
11-
#this needs to be in the top level CMakeLists.txt to enable tests
12-
include(CTest)
13-
14-
set(BOOST_ENABLE_CMAKE ON) set(Boost_CMAKE_CXX_STANDARD 17) set(
15-
CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED
16-
ON)
17-
18-
set(BOOST_INCLUDE_LIBRARIES iostreams uuid algorithm)
19-
20-
include(FetchContent) FetchContent_Declare(
21-
Boost GIT_REPOSITORY
22-
https : // github.com/boostorg/boost.git
23-
GIT_TAG boost -
24-
1.83.0 GIT_SHALLOW
25-
TRUE) FetchContent_MakeAvailable(Boost)
26-
27-
#compile the library
28-
add_subdirectory(src)
29-
30-
#compile the application
31-
add_subdirectory(app)
32-
33-
#optionally add doxygen target to generate documentation
34-
option(BUILD_DOCS
35-
"Enable building of "
36-
"documentation (requires "
37-
"Doxygen)" OFF) if (BUILD_DOCS)
38-
find_package(Doxygen REQUIRED) set(
39-
DOXYGEN_EXCLUDE_PATTERNS
40-
"${CMAKE_SOURCE_DIR}/ext/*")
41-
doxygen_add_docs(
42-
doxygen ${
43-
CMAKE_SOURCE_DIR} WORKING_DIRECTORY
44-
${CMAKE_CURRENT_BINARY_DIR})
45-
endif()
46-
47-
#compile the tests
48-
option(BUILD_TESTS
49-
"Enable "
50-
"building of "
51-
"documentation "
52-
"(requires "
53-
"Doxygen"
54-
")" OFF) if (BUILD_TESTS)
55-
add_subdirectory(
56-
tests) endif()
3+
# Set a name and a version number for your project:
4+
project(
5+
cpp-project-template
6+
VERSION 0.0.1
7+
LANGUAGES CXX)
8+
9+
set(CMAKE_AR /usr/bin/ar)
10+
11+
# Enable C language
12+
enable_language(C)
13+
14+
# this needs to be in the top level CMakeLists.txt to enable tests
15+
include(CTest)
16+
17+
set(BOOST_ENABLE_CMAKE ON)
18+
set(Boost_CMAKE_CXX_STANDARD 17)
19+
set(CMAKE_CXX_STANDARD 17)
20+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
21+
22+
set(BOOST_INCLUDE_LIBRARIES iostreams uuid algorithm)
23+
24+
include(FetchContent)
25+
FetchContent_Declare(
26+
Boost
27+
GIT_REPOSITORY https://github.com/boostorg/boost.git
28+
GIT_TAG boost-1.83.0
29+
GIT_SHALLOW TRUE)
30+
FetchContent_MakeAvailable(Boost)
31+
32+
# compile the library
33+
add_subdirectory(src)
34+
35+
# compile the application
36+
add_subdirectory(app)
37+
38+
# optionally add doxygen target to generate documentation
39+
option(BUILD_DOCS "Enable building of documentation (requires Doxygen)" OFF)
40+
if(BUILD_DOCS)
41+
find_package(Doxygen REQUIRED)
42+
set(DOXYGEN_EXCLUDE_PATTERNS "${CMAKE_SOURCE_DIR}/ext/*")
43+
doxygen_add_docs(doxygen ${CMAKE_SOURCE_DIR}
44+
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
45+
endif()
46+
47+
# compile the tests
48+
option(BUILD_TESTS "Enable building of documentation (requires Doxygen)" OFF)
49+
if(BUILD_TESTS)
50+
add_subdirectory(tests)
51+
endif()

app/gyt.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "commands/log.h"
66
#include "commands/ls-tree.h"
77
#include "commands/show-ref.h"
8+
#include "commands/tag.h"
89
#include <iostream>
910
#include <string>
1011
#include <vector>
@@ -33,6 +34,8 @@ int main(int argc, char **argv) {
3334
commands::checkout(args);
3435
} else if (command == "show-ref") {
3536
commands::showref(args);
37+
} else if (command == "tag") {
38+
commands::tag(args);
3639
} else {
3740
std::cerr << "Unknown command: " << command << "\n";
3841
return -1;

doc/case studies/Singleton.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Parser and singleton
2+
3+
`TagParser` is an example of a singleton class.
4+
It was largely adapted from: https://stackoverflow.com/questions/75249277/is-singleton-with-static-unique-ptr-a-good-practice
5+
6+
It stores a reference of itself as a static member. The constructor constructs and sets the CmdLine arguments.
7+
8+
Some learning points:
9+
- CmdLine, Arg classes has no default constructor, so they have to be constructed during the construction of `TagParser`, not after.
10+
- Using a `static` reference of itself.

include/commands/tag.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#ifndef TAG_H
2+
#define TAG_H
3+
4+
#include <string>
5+
#include <vector>
6+
namespace commands {
7+
void tag(std::vector<std::string> &args);
8+
} // namespace commands
9+
10+
#endif // TAG_H

include/parsers/TagParser.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#ifndef TAGPARSER_H
2+
#define TAGPARSER_H
3+
4+
#include "tclap/CmdLine.h"
5+
#include <string>
6+
#include <vector>
7+
8+
class TagParser {
9+
public:
10+
static TagParser &get();
11+
void parse(std::vector<std::string> &args);
12+
std::string getTag();
13+
bool isCommitSet() const;
14+
std::string getCommit();
15+
16+
private:
17+
// hides constructors to avoid accidental instantiation
18+
TagParser();
19+
TagParser(const TagParser &) = delete;
20+
TagParser &operator=(const TagParser &) = delete;
21+
TagParser(TagParser &&) = delete;
22+
TagParser &operator=(TagParser &&) = delete;
23+
~TagParser() = default;
24+
// data members
25+
TCLAP::CmdLine cmd;
26+
TCLAP::UnlabeledValueArg<std::string> tagArg;
27+
TCLAP::UnlabeledValueArg<std::string> commitArg;
28+
};
29+
30+
#endif // TAGPARSER_H

include/repository.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace fs = std::filesystem;
99
class GitRepository {
1010
public:
1111
GitRepository(fs::path worktree, fs::path gitdir);
12-
GitRepository(std::string path, bool force = false);
12+
GitRepository(const std::string &path, bool force = false);
1313

1414
static std::optional<GitRepository> find(const fs::path &path = fs::path("."),
1515
bool required = true);

include/util.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ fs::perms get_unix_permissions(int mode);
1616
std::string remove_file_prefix(const fs::path &path,
1717
const fs::path &repo_prefix);
1818
std::string resolve_ref(const fs::path &ref_path, GitRepository &repo);
19+
fs::path get_commit_path(const std::string &sha);
1920
#endif // UTIL_H

run.sh

100644100755
Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,31 @@
1-
#!/ bin / bash
2-
set - e
1+
#!/bin/bash
2+
set -e
33

44
#Set the default build type to Debug if no environment is specified
5-
BUILD_TYPE = ${BUILD_TYPE : -Debug}
5+
BUILD_TYPE=${BUILD_TYPE:-Debug}
66

77
#If the environment is specified as "prod", set the build type to Release
8-
if["$1" == "-p"];
9-
then BUILD_TYPE = Release fi
8+
if [ "$1" == "-p" ];
9+
then
10+
BUILD_TYPE=Release
11+
fi
1012

1113
#remove old build if any
12-
if[-f "app/gyt"];
13-
then rm -
14-
rf app / gyt fi
14+
if [ -f "app/gyt" ];
15+
then
16+
rm -rf app/gyt
17+
fi
1518

1619
#Print the selected build type
17-
echo "Selected build type: $BUILD_TYPE" echo
18-
"Building the project... This will take a while to install "
19-
"dependencies for the first time."
20+
echo "Selected build type: $BUILD_TYPE"
21+
echo "Building the project... This will take a while to install dependencies for the first time."
2022

2123
#Run CMake with the selected build type
22-
cmake..-
23-
DCMAKE_BUILD_TYPE = $BUILD_TYPE - DCMAKE_EXPORT_COMPILE_COMMANDS =
24-
1 -
25-
G Ninja
24+
cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -G Ninja
2625

2726
#Build the project
28-
ninja
27+
ninja
2928

3029
#symlink - so I can run it like gyt[arguments....]
31-
sudo rm /
32-
usr / local / bin / gyt sudo ln -
33-
s "$(pwd)/app/gyt" / usr / local / bin / gyt
30+
sudo rm /usr/local/bin/gyt
31+
sudo ln -s "$(pwd)/app/gyt" /usr/local/bin/gyt

src/CMakeLists.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,14 @@ target_link_libraries(object PRIVATE repository boost_libraries)
2424
target_include_directories(object PUBLIC ../include)
2525
target_compile_features(object PUBLIC cxx_std_17)
2626

27+
file(GLOB PARSER_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/parsers/*.cpp")
28+
add_library(parsers ${PARSER_SOURCES})
29+
target_link_libraries(parsers PRIVATE repository object boost_libraries)
30+
target_include_directories(parsers PUBLIC ../include)
31+
target_compile_features(parsers PUBLIC cxx_std_17)
32+
2733
file(GLOB COMMANDS_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/commands/*.cpp")
2834
add_library(commands ${COMMANDS_SOURCES})
29-
target_link_libraries(commands PRIVATE repository object boost_libraries)
35+
target_link_libraries(commands PRIVATE parsers repository object boost_libraries)
3036
target_include_directories(commands PUBLIC ../include)
3137
target_compile_features(commands PUBLIC cxx_std_17)

src/commands/tag.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#include "commands/tag.h"
2+
#include "object.h"
3+
#include "parsers/TagParser.h"
4+
#include "repository.h"
5+
#include "util.h"
6+
#include <optional>
7+
8+
namespace commands {
9+
void tag(std::vector<std::string> &args) {
10+
TagParser &parser = TagParser::get();
11+
parser.parse(args);
12+
// process args
13+
std::optional<GitRepository> repo = GitRepository::find();
14+
if (!repo) {
15+
throw std::runtime_error("Not a git repository");
16+
}
17+
// resolve ref to find the commit hash
18+
std::string commit = parser.isCommitSet() ? parser.getCommit()
19+
: GitObject::find(*repo, "HEAD");
20+
std::string tag = parser.getTag();
21+
// ERROR: if the commit doesn't exist.
22+
if (!fs::exists(repo->repo_path(get_commit_path(commit)))) {
23+
std::string error_message = commit + ": not a valid commit";
24+
throw std::runtime_error(error_message);
25+
}
26+
// ERROR: if the tag already exists
27+
fs::path tag_path = repo->repo_path("refs/tags/" + tag);
28+
if (fs::is_regular_file(tag_path)) {
29+
std::string error_message = "tag '" + tag + "' already exists";
30+
throw std::runtime_error(error_message);
31+
}
32+
33+
// create a new file in refs/tags/{name}, and write the sha of the commit.
34+
create_file(tag_path, commit + "\n");
35+
}
36+
} // namespace commands

src/object.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@ GitObject::GitObject(const std::string &format) { this->format = format; }
1818
void GitObject::init() {}
1919

2020
GitObject *GitObject::read(GitRepository &repo, const std::string &sha) {
21-
std::string dir = sha.substr(0, 2);
22-
std::string path = sha.substr(2);
23-
fs::path file_path = fs::path("objects") / dir / path;
21+
fs::path file_path = get_commit_path(sha);
2422
fs::path paths = repo.file(file_path);
2523

2624
if (!fs::is_regular_file(paths)) {

src/parsers/TagParser.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#include "parsers/TagParser.h"
2+
#include <string>
3+
#include <vector>
4+
5+
void TagParser::parse(std::vector<std::string> &args) {
6+
cmd.reset();
7+
cmd.parse(args);
8+
}
9+
10+
std::string TagParser::getTag() { return tagArg.getValue(); }
11+
bool TagParser::isCommitSet() const { return commitArg.isSet(); }
12+
std::string TagParser::getCommit() { return commitArg.getValue(); }
13+
14+
TagParser &TagParser::get() {
15+
static TagParser instance;
16+
return instance;
17+
}
18+
19+
TagParser::TagParser()
20+
: cmd("tag", ' ', "0.1"),
21+
tagArg("tagname", "Tag name", true, "HEAD", "tag name"),
22+
commitArg("commit",
23+
"Commit to tag to. If not specified, defaults to HEAD", false,
24+
"HEAD", "commit to tag") {
25+
cmd.ignoreUnmatched(true);
26+
cmd.add(tagArg);
27+
cmd.add(commitArg);
28+
}

src/repository.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
namespace fs = std::filesystem;
88

9-
GitRepository::GitRepository(std::string path, bool force)
9+
GitRepository::GitRepository(const std::string &path, bool force)
1010
: worktree(fs::path(path)), gitdir(fs::path(path) / ".git") {
1111
if (!force && !fs::is_directory(gitdir)) {
1212
std::cerr << "Not a git repository: " << gitdir << "\n";
@@ -111,4 +111,4 @@ std::optional<GitRepository> GitRepository::find(const fs::path &path,
111111
// TODO: update this to support refs
112112
void GitRepository::update_head(const std::string &new_head) {
113113
create_file(gitdir / "HEAD", new_head + "\n");
114-
}
114+
}

src/util.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,9 @@ std::string resolve_ref(const fs::path &ref_path, GitRepository &repo) {
169169
}
170170
return ref;
171171
}
172+
173+
fs::path get_commit_path(const std::string &sha) {
174+
std::string dir = sha.substr(0, 2);
175+
std::string path = sha.substr(2);
176+
return fs::path("objects") / dir / path;
177+
}

0 commit comments

Comments
 (0)