cppgit2 is a libgit2 wrapper library for use in modern C++ ( >= C++11). See the Build and Integration section for details on how to build and integrate cppgit2 in your projects.
This section presents some simple examples illustrating various cppgit2 features. You can find the full set of available examples in the /samples directory. Samples are still a work-in-progress. Pull requests are welcome here.
Initialize a new repository (git init)
To initialize a new repository, simply call repository::init.
#include<cppgit2/repository.hpp>usingnamespacecppgit2;intmain() {
auto repo = repository::init("hello_world", false);
}
If you want to create a bare repository, set the second argument to true.
Clone a repository and checkout specific branch (git clone --branch)
Let's say you want to clone a repository and checkout a specific branch. Construct an options object using clone::options, set the checkout branch name, and then use repository::clone to clone the repository.
#include<cppgit2/repository.hpp>usingnamespacecppgit2;intmain() {
auto url = "https://github.com/fffaraz/awesome-cpp";
auto branch_name = "gh-pages";
auto path = "awesome_cpp";
// Prepare clone options
clone::options options;
options.set_checkout_branch_name(branch_name);
// Clone repositoryauto repo = repository::clone(url, path, options);
}
Open an existing repository
You can open an existing repository with repository::open.
#include<cppgit2/repository.hpp>usingnamespacecppgit2;intmain() {
auto path = "~/dev/foo/bar"; // bar must contain a .git directoryauto repo = repository::open(path);
}
Use repository::open_bare to open a bare repository.
The repository class has a number of for_each_ methods that you can use to iterate over objects. Here's an example that iterates over all the branches in the repository.
Running this on the libgit2 repository yields the following:
$ ./build/samples/print_commits ext/libgit2
17223902 [GitHub] Merge pull request #5291 from libgit2/ethomson/0_99
b31cd05f [GitHub] Merge pull request #5372 from pks-t/pks/release-script
70062e28 [Patrick Steinhardt] version: update the version number to v0.99
a552c103 [Patrick Steinhardt] docs: update changelog for v0.99
1256b462 [GitHub] Merge pull request #5406 from libgit2/pks/azure-fix-arm32
5254c9bb [GitHub] Merge pull request #5398 from libgit2/pks/valgrind-openssl
e8660708 [GitHub] Merge pull request #5400 from lhchavez/fix-packfile-fuzzer
eaa70c6c [Patrick Steinhardt] tests: object: decrease number of concurrent cache accesses
01a83406 [Patrick Steinhardt] azure: docker: fix ARM builds by replacing gosu(1)
76b49caf [Patrick Steinhardt] azure: docker: synchronize Xenial/Bionic build instructions
f9985688 [Patrick Steinhardt] azure: docker: detect errors when building images
68bfacb1 [Patrick Steinhardt] azure: remove unused Linux setup script
795a5b2c [lhchavez] fuzzers: Fix the documentation
0119e57d [Patrick Steinhardt] streams: openssl: switch approach to silence Valgrind errors
...
...
...
Print Repository Tags (git tag)
The repository class has a number of for_each_ methods that you can use to iterate over objects. Here's an example that iterates over all the tags in the repository, printing the name and OID hash for each tag.
Here's a simplified implementation of git cat-file with cppgit2
#include<cppgit2/repository.hpp>
#include<cstdio>
#include<iomanip>
#include<iostream>usingnamespacecppgit2;voidprint_signature(const std::string &header, const signature &sig) {
char sign;
auto offset = sig.offset();
if (offset < 0) {
sign = '-';
offset = -offset;
} else {
sign = '+';
}
auto hours = offset / 60;
auto minutes = offset % 60;
std::cout << header << "" << sig.name() << "" << "<" << sig.email() << "> "
<< sig.time() << "" << sign;
std::cout << std::setfill('0') << std::setw(2) << hours;
std::cout << std::setfill('0') << std::setw(2) << minutes << std::endl;
}
// Printing out a blob is simple, get the contents and printvoidshow_blob(const blob &blob) {
std::fwrite(blob.raw_contents(), blob.raw_size(), 1, stdout);
}
// Show each entry with its type, id and attributesvoidshow_tree(const tree &tree) {
size_t count = tree.size();
for (size_t i = 0; i < tree.size(); ++i) {
auto entry = tree.lookup_entry_by_index(i);
std::cout << std::setfill('0') <<
std::oct << std::setw(6) << static_cast<git_filemode_t>(entry.filemode());
std::cout << "" << object::object_type_to_string(entry.type())
<< "" << entry.id().to_hex_string()
<< "\t" << entry.filename() << std::endl;
}
}
// Commits and tags have a few interesting fields in their header.voidshow_commit(const commit &commit) {
std::cout << "tree " << commit.tree_id().to_hex_string() << std::endl;
for (size_t i = 0; i < commit.parent_count(); ++i)
std::cout << "parent " << commit.parent_id(i).to_hex_string() << std::endl;
print_signature("author", commit.author());
print_signature("committer", commit.committer());
auto message = commit.message();
if (!message.empty())
std::cout << "\n" << message << std::endl;
}
voidshow_tag(const tag &tag) {
std::cout << "object " << tag.id().to_hex_string() << std::endl;
std::cout << "type " << object::object_type_to_string(tag.target_type()) << std::endl;
std::cout << "tag " << tag.name() << std::endl;
print_signature("tagger", tag.tagger());
auto tag_message = tag.message();
if (!tag_message.empty())
std::cout << "\n" << tag_message << std::endl;
}
intmain(int argc, char **argv) {
if (argc == 3) {
auto repo_path = repository::discover_path(".");
auto repo = repository::open(repo_path);
enumclassactions { size, type, pretty };
actions action;
if (strncmp(argv[1], "-s", 2) == 0) {
action = actions::size;
} elseif (strncmp(argv[1], "-t", 2) == 0) {
action = actions::type;
} elseif (strncmp(argv[1], "-p", 2) == 0) {
action = actions::pretty;
}
auto revision_str = argv[2];
auto object = repo.revparse_to_object(revision_str);
switch(action) {
case actions::type:
std::cout << object::object_type_to_string(object.type()) << std::endl;
break;
case actions::size:
std::cout << repo.odb().read(object.id()).size() << std::endl;
break;
case actions::pretty:
switch(object.type()) {
case object::object_type::blob:
show_blob(object.as_blob());
break;
case object::object_type::commit:
show_commit(object.as_commit());
break;
case object::object_type::tree:
show_tree(object.as_tree());
break;
case object::object_type::tag:
show_tag(object.as_tag());
break;
default:
std::cout << "unknown " << revision_str << std::endl;
break;
}
break;
}
} else {
std::cout << "Usage: ./executable (-s | -t | -p) <object>\n";
}
}
Running this sample on one of the libgit2 commits yields the following:
$ ./cat_file -p 01a8340662749943f3917505dc8ca65006495bec
tree 83d9bef2675178eeb3aa61d17e5c8b0f7b0ec1de
parent 76b49caf6a208e44d19c84caa6d42389f0de6194
author Patrick Steinhardt <[email protected]> 1582035643 +0100
committer Patrick Steinhardt <[email protected]> 1582040632 +0100
azure: docker: fix ARM builds by replacing gosu(1)
Our nightly builds are currently failing due to our ARM-based jobs.
These jobs crash immediately when entering the Docker container with a
exception thrown by Go's language runtime. As we're able to successfully
builds the Docker images in previous steps, it's unlikely to be a bug in
Docker itself. Instead, this exception is thrown by gosu(1), which is a
Go-based utility to drop privileges and run by our entrypoint.
Fix the issue by dropping gosu(1) in favor of sudo(1).
$ ./cat_file -p 83d9bef2675178eeb3aa61d17e5c8b0f7b0ec1de
100644 blob fd8430bc864cfcd5f10e5590f8a447e01b942bfe .HEADER
100644 blob 34c5e9234ec18c69a16828dbc9633a95f0253fe9 .editorconfig
100644 blob 176a458f94e0ea5272ce67c36bf30b6be9caf623 .gitattributes
040000 tree e8bfe5af39579a7e4898bb23f3a76a72c368cee6 .github
100644 blob dec3dca06c8fdc1dd7d426bb148b7f99355eaaed .gitignore
100644 blob 0b16a7e1f1a368d5ca42d580ba2256d1faecddb8 .mailmap
100644 blob 784bab3ee7da6133af679cae7527c4fe4a99b949 AUTHORS
100644 blob 8765a97b5b120259dd59262865ce166f382c0f9e CMakeLists.txt
100644 blob c0f61fb9158945f7b41abfd640630c914b2eb8d9 COPYING
100644 blob 9dafffec02ef8d9cf8b97f547444f989ddbfa298 README.md
100644 blob f98eebf505a37f756e0ad9d7cc4744397368c436 SECURITY.md
100644 blob bf733273b8cd8b601aaee9a5c10d099a7f6a87e2 api.docurium
100644 blob 2b593dd2cc2c2c252548c7fae4d469c11dd08430 azure-pipelines.yml
040000 tree d9aba7f7d7e9651c176df311dd0489e89266b2b4 azure-pipelines
040000 tree 64e8fd349c9c1dd20f810c22c4e62fe52aab5f18 cmake
040000 tree 5c640a5abe072362ca4bbcf66ef66617c0be0466 deps
040000 tree c84b6d0def9b4b790ece70c7ee68aa3fdf6caa85 docs
040000 tree f852bee8c6bcc3e456f19aff773079eb30abf747 examples
040000 tree 37aaf5d4a9fb0d89d2716236c49474030e36dc93 fuzzers
100644 blob 905bdd24fa23c4d1a03e400a2ae8ecc639769da3 git.git-authors
040000 tree 7fdd111f708aad900604883ce1c161daf64ebb2d include
100644 blob d33f31c303663dbdbb4baed08ec3cd6c83116367 package.json
040000 tree 97afcc9b6e4ca91001aadf8a3414d043f22918cf script
040000 tree a08bd8a57d619b736ad2c300614b36ead8d0a333 src
040000 tree dcf5925f8bbda8062ef26ca427c5110868a7f041 tests
$ ./cat_file -s 8765a97b5b120259dd59262865ce166f382c0f9e
11957
Design Notes
Interoperability with libgit2
Most cppgit2 data structures can be constructed using a libgit2 C pointer.
Similarly, a libgit2 C pointer can be extracted from its wrapping cppgit2 data structure using the .c_ptr() method.
// Construct cppgit2 OID object
oid oid1("f9de917ac729414151fdce077d4098cfec9a45a5");
// Access libgit2 C ptrconst git_oid *oid1_cptr = oid1.c_ptr();
// Use the libgit2 C API to formatsize_t n = 8;
char * oid1_formatted = (char *)malloc(sizeof(char) * n);
git_oid_tostr(oid1_formatted, n + 1, oid1_cptr);
// Results are the sameREQUIRE(oid1.to_hex_string(8) == std::string(oid1_formatted)); // f9de917
Ownership and Memory Management
libgit2 sometimes allocates memory and returns pointers to data structures that are owned by the user (required to be free'd by the user), and at other times returns a pointer to memory that is managed by the libgit2 layer.
To properly cleanup memory that is owned by the user, use the ownership enum to explicitly specify the ownership when wrapping.
cppgit2::tree tree1(&tree_cptr, ownership::user);
If the pointer being wrapped is owned by the user, the class destructor will call git_<type>_free on the pointer and clean up properly. If you specify the ownership as ownership::libgit2, the pointer is left alone.
At the moment, cppgit2 throws a custom git_exception anytime the return value from libgit2 indicates that an error has occurred. Typically libgit2 functions respond with a return code (0 = good, anything else = error) and git_error_last provides the most recent error message. cppgit2 uses this message when constructing the git_exception exception.
Here's a typical example of a wrapped function:
voidrepository::delete_reflog(const std::string &name) {
if (git_reflog_delete(c_ptr_, name.c_str()))
throwgit_exception();
}
where git_exception initializes its what() message like so:
Contributions are welcome, have a look at the CONTRIBUTING.md document for more information. If you notice any bugs while using/reviewing cppgit2, please report them. Suggestions w.r.t improving the code quality are also welcome.
terminate called after throwing an instance of 'cppgit2::git_exception'
what(): failed to create commit: current tip is not the first parent
Aborted (core dumped)
Ok, no problem. Looks like libgit wants us to put head commit to the list of parents. So, I changed your example code to:
when trying to debug my application, I was required to copy git2.dll and libgit2_clar.exe into my working directory so they are in view of my program. However now when I do anything after git_libgit2_init(); I get read access violations. Please advise.
Exception thrown at 0x00000000009938D4 in GitEconomyCommit.exe: 0xC0000005: Access violation executing location 0x00000000009938D4.
Hi, I got the following error when I was trying to execute clone_and_checkout_branch.cpp sample. I use an ArchLinux distribution with libsecret. So, I already have the /usr/lib/git-core/git-credential-libsecret installed:
terminate called after throwing an instance of 'cppgit2::git_exception'
what(): reference 'refs/remotes/origin/usr/lib/git-core/git-credential-libsecret' not found
Here, repository wraps a c-struct pointer that is copied to the return value. As the result variable goes out of scope, it deletes the wrapped pointer, leaving the returned struct with a dangling c_ptr_. The main issue is a lack of move, move assignment and copy constructors. This problem affects at least 30 classes.
Here, a temporary strarray wrapper object is constructed to convert the vector into a c-struct. The internal c-struct pointer is then copied. However, the pointer is always deleted by the strarray wrapper class, leaving c_ptr_->paths dangling. The issue here is that the pointed-to obejct is not moved or copied out of strarray. This problem affects at least 10 usages of strarray::c_ptr().
Conclusion
Until these problems are fixed, it is quite dangerous to use this library.
I have a commit here that fixes at least the missing move, move assignment and copy ctors: 0fb889f
But the c_ptr() usage is still unfixed, and I am not sure which other c_ptr() apart from strarray have the same problem in their usage.