Last active
March 8, 2019 20:43
-
-
Save TheVice/950fd83f342c773863315a2b919c19f3 to your computer and use it in GitHub Desktop.
git_extractor – tool that browse repository at specific revision and save all files from that state to output path. Sample using: ‘git_extractor <path to repository> <hash of commit> <output path>’.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<Project DefaultTargets="Bootstrap" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |
<PropertyGroup Label="libgit2"> | |
<libgit2_version Condition="'$(libgit2_version)'=='' And '$(libgit2_url)'==''">0.27.8</libgit2_version> | |
<libgit2_url Condition="'$(libgit2_url)'=='' And '$(libgit2_version)'=='0.28.1'">https://github.com/libgit2/libgit2/archive/v0.28.1.zip</libgit2_url> | |
<libgit2_url Condition="'$(libgit2_url)'=='' And '$(libgit2_version)'=='0.28.0'">https://github.com/libgit2/libgit2/archive/v0.28.0.zip</libgit2_url> | |
<libgit2_url Condition="'$(libgit2_url)'=='' And '$(libgit2_version)'=='0.27.8'">https://github.com/libgit2/libgit2/archive/v0.27.8.zip</libgit2_url> | |
<libgit2_zip Condition="'$(libgit2_zip)'==''"></libgit2_zip> | |
</PropertyGroup> | |
<PropertyGroup Label="cmake"> | |
<cmake_exe Condition="'$(cmake_exe)'=='' And '$(OS)'=='Unix'">/usr/local/bin/cmake</cmake_exe> | |
<cmake_exe Condition="!Exists('$(cmake_exe)') And '$(OS)'=='Unix'">cmake</cmake_exe> | |
<cmake_exe Condition="'$(cmake_exe)'=='' And '$(OS)'!='Unix' And '$(VCTargetsPath)'!=''">$([System.IO.Path]::GetFullPath('$(VCTargetsPath)..\..\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe'))</cmake_exe> | |
<cmake_exe Condition="'$(cmake_exe)'=='' And '$(OS)'!='Unix' And '$(MSBuildExtensionsPath)'!=''">$([System.IO.Path]::GetFullPath('$(MSBuildExtensionsPath)..\..\..\Microsoft Visual Studio\2017\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe'))</cmake_exe> | |
<cmake_exe Condition="!Exists('$(cmake_exe)') And '$(OS)'!='Unix'">cmake.exe</cmake_exe> | |
<VS15_x86>Visual Studio 15 2017</VS15_x86> | |
<VS15_x64>Visual Studio 15 2017 Win64</VS15_x64> | |
<!--VS16>Visual Studio 16 2019</VS16--> | |
<Unix_Makefiles>Eclipse CDT4 - Unix Makefiles</Unix_Makefiles> | |
<cmake_generator_name Condition="'$(cmake_generator_name)'=='' And '$(MSBuildRuntimeType)'=='Core' And '$(OS)'!='Unix'">$(VS15_x86)</cmake_generator_name> | |
<cmake_generator_name Condition="'$(cmake_generator_name)'=='' And '$(MSBuildRuntimeType)'!='Core' And !$([System.Environment]::Is64BitOperatingSystem)">$(VS15_x86)</cmake_generator_name> | |
<cmake_generator_name Condition="'$(cmake_generator_name)'=='' And '$(MSBuildRuntimeType)'!='Core' And $([System.Environment]::Is64BitOperatingSystem)">$(VS15_x64)</cmake_generator_name> | |
<cmake_generator_name Condition="'$(cmake_generator_name)'=='' And '$(OS)'=='Unix'">$(Unix_Makefiles)</cmake_generator_name> | |
<cmake_output_directory Condition="'$(cmake_output_directory)'==''">$(MSBuildThisFileDirectory)$(cmake_generator_name.Replace(" ", "_"))</cmake_output_directory> | |
<CONFIG Condition="'$(CONFIG)'==''">Release</CONFIG> | |
</PropertyGroup> | |
<Target Name="download_zip_archives" Condition="15.8 < $(MSBuildVersion)"> | |
<DownloadFile | |
Condition="'$(libgit2_zip)'==''" | |
SourceUrl="$(libgit2_url)" | |
DestinationFolder="$(MSBuildThisFileDirectory)"> | |
<Output TaskParameter="DownloadedFile" PropertyName="libgit2_zip" /> | |
</DownloadFile> | |
</Target> | |
<Target Name="unzip_archives" Condition="15.8 < $(MSBuildVersion)" DependsOnTargets="download_zip_archives"> | |
<Unzip | |
Condition="Exists('$(libgit2_zip)')" | |
ContinueOnError="true" | |
OverwriteReadOnlyFiles="true" | |
SourceFiles="$(libgit2_zip)" | |
DestinationFolder="$(MSBuildThisFileDirectory)" /> | |
</Target> | |
<Target Name="CMake" Condition="Exists('$(cmake_exe)')" DependsOnTargets="unzip_archives"> | |
<PropertyGroup> | |
<source_directory>$(MSBuildThisFileDirectory)</source_directory> | |
<libgit2_folder Condition="'$(libgit2_zip)'!=''">$(MSBuildThisFileDirectory)$([System.IO.Path]::GetFileNameWithoutExtension('$(libgit2_zip)'))</libgit2_folder> | |
<libgit2_folder Condition="'$(libgit2_zip)'==''">$(MSBuildThisFileDirectory)$([System.IO.Path]::GetFileNameWithoutExtension('$(libgit2_url)'))</libgit2_folder> | |
</PropertyGroup> | |
<MakeDir | |
Condition="Exists('$(libgit2_folder)') And !Exists('$(cmake_output_directory)')" | |
Directories="$(cmake_output_directory)" /> | |
<Exec | |
Condition="Exists('$(libgit2_folder)')" | |
Command=""$(cmake_exe)" -G "$(cmake_generator_name)" -DLIBGIT2_PATH="$(libgit2_folder)" "$(source_directory)"" | |
WorkingDirectory="$(cmake_output_directory)" /> | |
<Exec | |
Condition="Exists('$(libgit2_folder)')" | |
Command=""$(cmake_exe)" --build "$(cmake_output_directory)" --config $(CONFIG)" /> | |
</Target> | |
<Target Name="Bootstrap" DependsOnTargets="CMake" /> | |
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
cmake_minimum_required(VERSION 3.8) | |
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) | |
message(FATAL_ERROR "Configuration process cannot start from project source directory.") | |
endif() | |
project("git_extractor") | |
if(DEFINED ENV{LIBGIT2_PATH}) | |
file(TO_CMAKE_PATH $ENV{LIBGIT2_PATH} libgit2_path) | |
elseif(DEFINED LIBGIT2_PATH) | |
file(TO_CMAKE_PATH ${LIBGIT2_PATH} libgit2_path) | |
else() | |
message(FATAL_ERROR "LIBGIT2_PATH not set. Can be found here - https://github.com/libgit2/libgit2/releases/") | |
endif() | |
option(BUILD_CLAR "" OFF) | |
option(USE_SSH "" OFF) | |
option(USE_HTTPS "" OFF) | |
option(CURL "" OFF) | |
option(USE_EXT_HTTP_PARSER "" OFF) | |
add_subdirectory("${libgit2_path}" ${CMAKE_BINARY_DIR}/libgit2) | |
add_executable(${PROJECT_NAME}_c "${CMAKE_SOURCE_DIR}/${PROJECT_NAME}.c") | |
add_executable(${PROJECT_NAME}_cpp "${CMAKE_SOURCE_DIR}/${PROJECT_NAME}.cpp") | |
target_include_directories(${PROJECT_NAME}_c SYSTEM PRIVATE "${libgit2_path}/include") | |
target_link_libraries(${PROJECT_NAME}_c PRIVATE git2) | |
target_include_directories(${PROJECT_NAME}_cpp SYSTEM PRIVATE "${libgit2_path}/include") | |
target_link_libraries(${PROJECT_NAME}_cpp PRIVATE git2) | |
add_custom_command(TARGET ${PROJECT_NAME}_c POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "$<TARGET_FILE:git2>" "$<TARGET_FILE_DIR:${PROJECT_NAME}_c>/$<TARGET_FILE_NAME:git2>") | |
add_custom_command(TARGET ${PROJECT_NAME}_cpp POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "$<TARGET_FILE:git2>" "$<TARGET_FILE_DIR:${PROJECT_NAME}_cpp>/$<TARGET_FILE_NAME:git2>") | |
if(NOT MSVC) | |
set_target_properties(${PROJECT_NAME}_c PROPERTIES C_STANDARD 11) | |
set_target_properties(${PROJECT_NAME}_cpp PROPERTIES CXX_STANDARD 11) | |
endif() | |
if(MSVC) | |
set(FLAGS "${FLAGS} /W4 /GS") | |
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${DEFAULT_CMAKE_C_FLAGS} ${FLAGS}") | |
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${DEFAULT_CMAKE_CXX_FLAGS} ${FLAGS}") | |
if(CMAKE_CL_64) | |
set(LINK_FLAGS "${LINK_FLAGS} /DynamicBase /NXCompat") | |
else() | |
set(LINK_FLAGS "${LINK_FLAGS} /SafeSEH /DynamicBase /NXCompat") | |
endif() | |
else() | |
set(FLAGS "${FLAGS} -Wall -Wextra -Werror -Wno-unused-parameter -Wno-unknown-pragmas") | |
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${DEFAULT_CMAKE_C_FLAGS} ${FLAGS}") | |
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${DEFAULT_CMAKE_CXX_FLAGS} ${FLAGS}") | |
endif() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* The MIT License (MIT) | |
* | |
* Copyright (c) 2019 https://github.com/TheVice/ | |
* | |
*/ | |
#include <git2.h> | |
#include <time.h> | |
#include <stdio.h> | |
#include <string.h> | |
#include <stdlib.h> | |
#include <assert.h> | |
#ifdef _WIN32 | |
#include <windows.h> | |
#else | |
#include <dirent.h> | |
#include <sys/types.h> | |
#include <sys/stat.h> | |
#endif | |
char write_to_stream(FILE* stream, const char* content, size_t content_size) | |
{ | |
return (content_size == fwrite(content, 1, content_size, stream)) ? 1 : 0; | |
} | |
char write_to_file(const char* file_name, const char* content, size_t content_size) | |
{ | |
#if defined(__STDC_LIB_EXT1__) || (defined(_MSC_VER) && (_MSC_VER >= 1400)) | |
FILE* stream = NULL; | |
if (0 != fopen_s(&stream, file_name, "w")) | |
{ | |
return 0; | |
} | |
#else | |
FILE* stream = fopen(file_name, "w"); | |
#endif | |
if (NULL != stream) | |
{ | |
const char result = write_to_stream(stream, content, content_size); | |
return (result && 0 != fclose(stream)) ? 1 : 0; | |
} | |
return 0; | |
} | |
#ifdef _WIN32 | |
char is_path_a_directory(const char* path) | |
{ | |
char is_directory = 0; | |
WIN32_FIND_DATAA file_data; | |
memset(&file_data, 0, sizeof(file_data)); | |
HANDLE file_handle = FindFirstFileA(path, &file_data); | |
if (INVALID_HANDLE_VALUE != file_handle) | |
{ | |
is_directory = ((0 != (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) ? 1 : 0); | |
FindClose(file_handle); | |
memset(&file_data, 0, sizeof(file_data)); | |
file_handle = INVALID_HANDLE_VALUE; | |
} | |
return is_directory; | |
} | |
char create_a_directory_(const char* path) | |
{ | |
const BOOL status = CreateDirectoryA(path, NULL); | |
return 0 != status; | |
} | |
#else | |
char is_path_a_directory(const char* path) | |
{ | |
char is_directory = 0; | |
DIR* dir = NULL; | |
if (NULL != (dir = opendir(path))) | |
{ | |
closedir(dir); | |
dir = NULL; | |
is_directory = 1; | |
} | |
return is_directory; | |
} | |
char create_a_directory_(const char* path) | |
{ | |
static const mode_t mode = S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH; | |
const int status = mkdir(path, mode); | |
return ((0 == status) ? 1 : 0); | |
} | |
#endif | |
char create_a_directory(const char* path) | |
{ | |
if (0 == strlen(path) || | |
0 == strcmp("/", path) || | |
is_path_a_directory(path) || | |
create_a_directory_(path)) | |
{ | |
return 1; | |
} | |
const size_t size = strlen(path); | |
const char* pos = &path[0]; | |
char is_created = 0; | |
char current_directory[FILENAME_MAX]; | |
while (NULL != (pos = strstr(pos, "/"))) | |
{ | |
const size_t distance = (pos - &path[0]); | |
assert(distance < FILENAME_MAX); | |
if (FILENAME_MAX <= distance) | |
{ | |
is_created = 0; | |
break; | |
} | |
memcpy(current_directory, path, distance); | |
current_directory[distance] = '\0'; | |
if (distance < size) | |
{ | |
++pos; | |
} | |
if (is_path_a_directory(current_directory)) | |
{ | |
continue; | |
} | |
if ((0 == (is_created = create_a_directory_(current_directory))) || (size <= distance)) | |
{ | |
break; | |
} | |
} | |
if (!is_created) | |
{ | |
return 0; | |
} | |
return create_a_directory_(path); | |
} | |
char git_error_info(int error) | |
{ | |
char result = 0; | |
if (error < 0) | |
{ | |
const git_error* err = giterr_last(); | |
/**/ | |
fprintf(stderr, "[Error][libgit2] '%i/%i' '%s'.", error, err->klass, err->message); | |
/**/ | |
++result; | |
} | |
return result; | |
} | |
size_t get_blob_content(const git_blob* blob, char* content) | |
{ | |
const git_off_t blob_size = git_blob_rawsize(blob); | |
if (0 < blob_size) | |
{ | |
if (NULL != content) | |
{ | |
memcpy(content, git_blob_rawcontent(blob), (size_t)blob_size); | |
} | |
return (size_t)blob_size; | |
} | |
return 0; | |
} | |
static char* content = NULL; | |
static size_t content_capacity = 0; | |
typedef struct | |
{ | |
char* real_root; | |
git_repository* repo; | |
} walk_data; | |
int walk_cb(const char* root, | |
const git_tree_entry* entry, | |
void* payload) | |
{ | |
(void)root; | |
walk_data* data = (walk_data*)payload; | |
const char* entry_name = git_tree_entry_name(entry); | |
const size_t real_root_size = strlen(data->real_root); | |
const size_t entry_name_size = strlen(entry_name); | |
size_t offset = 0; | |
char* full_name = (char*)malloc(real_root_size + 1 + entry_name_size + 1); | |
if (NULL == full_name) | |
{ | |
return -1; | |
} | |
if (0 < real_root_size) | |
{ | |
offset = real_root_size; | |
memcpy(full_name, data->real_root, real_root_size); | |
full_name[offset] = '/'; | |
++offset; | |
} | |
if (0 < entry_name_size) | |
{ | |
memcpy(&full_name[offset], entry_name, entry_name_size); | |
full_name[offset + entry_name_size] = '\0'; | |
} | |
const git_otype entry_type = git_tree_entry_type(entry); | |
if (GIT_OBJ_TREE == entry_type) | |
{ | |
walk_data sub_payload; | |
sub_payload.real_root = full_name; | |
sub_payload.repo = data->repo; | |
/**/ | |
git_tree* sub_tree = NULL; | |
if (git_error_info(git_tree_lookup(&sub_tree, sub_payload.repo, git_tree_entry_id(entry)))) | |
{ | |
free(full_name); | |
full_name = NULL; | |
/**/ | |
return -1; | |
} | |
const int result = git_error_info(git_tree_walk(sub_tree, GIT_TREEWALK_PRE, walk_cb, &sub_payload)); | |
git_tree_free(sub_tree); | |
sub_tree = NULL; | |
if (1 == result) | |
{ | |
free(full_name); | |
full_name = NULL; | |
/**/ | |
return -1; | |
} | |
} | |
else if (GIT_OBJ_BLOB == entry_type) | |
{ | |
fprintf(stdout, "%s\n", full_name); | |
git_object* obj = NULL; | |
if (git_error_info(git_tree_entry_to_object(&obj, data->repo, entry))) | |
{ | |
free(full_name); | |
full_name = NULL; | |
/**/ | |
return -1; | |
} | |
size_t blob_size = get_blob_content((git_blob*)obj, NULL); | |
if (0 < blob_size) | |
{ | |
if (NULL == content) | |
{ | |
content = (char*)malloc(blob_size); | |
content_capacity = blob_size; | |
} | |
else if (content_capacity < blob_size) | |
{ | |
void* new_content = realloc(content, blob_size); | |
if (NULL == new_content) | |
{ | |
free(content); | |
content = NULL; | |
content_capacity = 0; | |
// | |
git_object_free(obj); | |
obj = NULL; | |
// | |
free(full_name); | |
full_name = NULL; | |
/**/ | |
return -1; | |
} | |
content = (char*)new_content; | |
content_capacity = blob_size; | |
} | |
blob_size = get_blob_content((git_blob*)obj, content); | |
} | |
git_object_free(obj); | |
obj = NULL; | |
if (0 < blob_size) | |
{ | |
if (0 < real_root_size) | |
{ | |
if (!create_a_directory(data->real_root)) | |
{ | |
fprintf(stderr, "[Warning]: Unable to create directory '%s'.", data->real_root); | |
} | |
} | |
if (!write_to_file(full_name, content, blob_size)) | |
{ | |
fprintf(stderr, "[Warning]: Unable write to file '%s'.", full_name); | |
} | |
} | |
} | |
free(full_name); | |
full_name = NULL; | |
/**/ | |
return 1; | |
} | |
char save_files_from_revision( | |
const char* path_to_repository, | |
const char* commit_sha, | |
const char* output_directory) | |
{ | |
git_oid oid; | |
if (git_error_info(git_oid_fromstr(&oid, commit_sha))) | |
{ | |
return 0; | |
} | |
git_repository* repo = NULL; | |
if (git_error_info(git_repository_open(&repo, path_to_repository))) | |
{ | |
return 0; | |
} | |
git_commit* commit = NULL; | |
if (git_error_info(git_commit_lookup(&commit, repo, &oid))) | |
{ | |
git_repository_free(repo); | |
repo = NULL; | |
/**/ | |
return 0; | |
} | |
const char* summary = git_commit_summary(commit); | |
const git_time_t time = git_commit_time(commit); | |
#if defined(__STDC_LIB_EXT1__) || (defined(_MSC_VER) && (_MSC_VER >= 1400)) | |
#define str_time_size 32 | |
char str_time[str_time_size]; | |
if (0 != ctime_s(str_time, str_time_size, &time)) | |
{ | |
str_time[0] = '\0'; | |
} | |
#else | |
char* str_time = ctime(&time); | |
#endif | |
for (size_t i = 0, count = strlen(str_time); i < count; ++i) | |
{ | |
if ('\n' == str_time[i]) | |
{ | |
str_time[i] = '\0'; | |
break; | |
} | |
} | |
const git_signature* committer = git_commit_committer(commit); | |
const git_signature* author = git_commit_author(commit); | |
/**/ | |
fprintf(stdout, "Commit : '%s'\n", commit_sha); | |
fprintf(stdout, "Author : '%s'\n", author->name); | |
fprintf(stdout, "Committer : '%s'\n", committer->name); | |
fprintf(stdout, "Date : '%s'\n", str_time); | |
fprintf(stdout, "Summary : '%s'\n", summary); | |
/**/ | |
git_tree* tree = NULL; | |
if (git_error_info(git_commit_tree(&tree, commit))) | |
{ | |
git_commit_free(commit); | |
commit = NULL; | |
/**/ | |
git_repository_free(repo); | |
repo = NULL; | |
/**/ | |
return 0; | |
} | |
walk_data payload; | |
const size_t output_directory_size = strlen(output_directory); | |
const size_t commit_sha_size = strlen(commit_sha); | |
payload.real_root = (char*)malloc(output_directory_size + 1 + commit_sha_size + 1); | |
if (NULL == payload.real_root) | |
{ | |
git_commit_free(commit); | |
commit = NULL; | |
/**/ | |
git_repository_free(repo); | |
repo = NULL; | |
/**/ | |
return 0; | |
} | |
size_t offset = 0; | |
if (0 < output_directory_size) | |
{ | |
offset = output_directory_size; | |
memcpy(payload.real_root, output_directory, output_directory_size); | |
payload.real_root[offset] = '/'; | |
++offset; | |
} | |
if (0 < commit_sha_size) | |
{ | |
memcpy(&payload.real_root[offset], commit_sha, commit_sha_size); | |
payload.real_root[offset + commit_sha_size] = '\0'; | |
} | |
payload.repo = repo; | |
/**/ | |
const char result = git_error_info(git_tree_walk(tree, GIT_TREEWALK_PRE, walk_cb, &payload)); | |
if (NULL != content) | |
{ | |
free(content); | |
content = NULL; | |
content_capacity = 0; | |
} | |
free(payload.real_root); | |
payload.real_root = NULL; | |
/**/ | |
git_tree_free(tree); | |
tree = NULL; | |
/**/ | |
git_commit_free(commit); | |
commit = NULL; | |
/**/ | |
git_repository_free(repo); | |
repo = NULL; | |
/**/ | |
return (1 == result) ? 0 : 1; | |
} | |
int main(int argc, char** argv) | |
{ | |
if (argc != 4 || git_error_info(git_libgit2_init())) | |
{ | |
return EXIT_FAILURE; | |
} | |
const char result = save_files_from_revision(argv[1], argv[2], argv[3]); | |
if (git_error_info(git_libgit2_shutdown()) || 0 == result) | |
{ | |
return EXIT_FAILURE; | |
} | |
return EXIT_SUCCESS; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* The MIT License (MIT) | |
* | |
* Copyright (c) 2019 https://github.com/TheVice/ | |
* | |
*/ | |
#include <git2.h> | |
#include <ctime> | |
#include <string> | |
#include <cstring> | |
#include <cstdlib> | |
#include <fstream> | |
#include <iostream> | |
#include <algorithm> | |
#ifdef _WIN32 | |
#include <windows.h> | |
#else | |
#include <ftw.h> | |
#include <dirent.h> | |
#include <sys/types.h> | |
#include <sys/stat.h> | |
#endif | |
bool write_to_stream(std::ostream& stream, const std::string& content) | |
{ | |
return !std::copy(content.cbegin(), content.cend(), | |
std::ostreambuf_iterator<char>(stream)).failed(); | |
} | |
bool write_to_file(const std::string& file_name, const std::string& content) | |
{ | |
std::ofstream file_stream(file_name); | |
if (file_stream.is_open()) | |
{ | |
return write_to_stream(file_stream, content); | |
} | |
return false; | |
} | |
#ifdef _WIN32 | |
bool is_path_a_directory(const std::string& path) | |
{ | |
auto is_directory = false; | |
WIN32_FIND_DATAA file_data; | |
std::memset(&file_data, 0, sizeof(file_data)); | |
auto file_handle = FindFirstFileA(path.c_str(), &file_data); | |
if (INVALID_HANDLE_VALUE != file_handle) | |
{ | |
is_directory = (0 != (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)); | |
FindClose(file_handle); | |
std::memset(&file_data, 0, sizeof(file_data)); | |
file_handle = INVALID_HANDLE_VALUE; | |
} | |
return is_directory; | |
} | |
bool create_a_directory_(const std::string& path) | |
{ | |
const auto status = CreateDirectoryA(path.c_str(), nullptr); | |
return 0 != status; | |
} | |
#else | |
bool is_path_a_directory(const std::string& path) | |
{ | |
auto is_directory = false; | |
DIR* dir = nullptr; | |
if (nullptr != (dir = opendir(path.c_str()))) | |
{ | |
closedir(dir); | |
dir = nullptr; | |
is_directory = true; | |
} | |
return is_directory; | |
} | |
bool create_a_directory_(const std::string& path) | |
{ | |
static const auto mode = S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH; | |
const auto status = mkdir(path.c_str(), mode); | |
return 0 == status; | |
} | |
#endif // _WIN32 | |
bool create_a_directory(const std::string& path) | |
{ | |
if (path.empty() || | |
"/" == path || | |
is_path_a_directory(path) || | |
create_a_directory_(path)) | |
{ | |
return true; | |
} | |
auto pos = path.cbegin(); | |
auto is_created = false; | |
std::string current_directory(FILENAME_MAX, '\0'); | |
while (path.cend() != (pos = std::find_if(pos, path.cend(), [](const char& ch) | |
{ | |
return '/' == ch; | |
}))) | |
{ | |
current_directory.clear(); | |
// | |
std::copy( | |
path.cbegin(), | |
pos, | |
std::back_inserter(current_directory)); | |
// | |
std::advance(pos, 1); | |
if (is_path_a_directory(current_directory)) | |
{ | |
continue; | |
} | |
if (false == (is_created = create_a_directory_(current_directory))) | |
{ | |
break; | |
} | |
} | |
if (!is_created) | |
{ | |
return false; | |
} | |
return create_a_directory_(path); | |
} | |
bool git_error_info(int error) | |
{ | |
if (error < 0) | |
{ | |
const auto err = giterr_last(); | |
// | |
std::cerr << "[Error][libgit2]: '"; | |
std::cerr << std::to_string(error) + "/" + std::to_string(err->klass); | |
std::cerr << "' '" << err->message << "'."; | |
// | |
return true; | |
} | |
return false; | |
} | |
std::size_t get_blob_content(const git_blob* blob, std::string& content) | |
{ | |
const auto blob_size = git_blob_rawsize(blob); | |
if (0 < blob_size) | |
{ | |
content.resize(static_cast<std::size_t>(blob_size)); | |
std::memcpy(&content.front(), git_blob_rawcontent(blob), content.size()); | |
} | |
else | |
{ | |
content.clear(); | |
} | |
return content.size(); | |
} | |
struct walk_data | |
{ | |
static std::string content; | |
const std::string real_root; | |
git_repository* const repo; | |
}; | |
std::string walk_data::content; | |
int walk_cb(const char* root, | |
const git_tree_entry* entry, | |
void* payload) | |
{ | |
(void)root; | |
const auto entry_name = git_tree_entry_name(entry); | |
auto data = (walk_data*)payload; | |
const std::string full_name( | |
(data->real_root.empty() ? "" : data->real_root + "/") + entry_name); | |
const auto entry_type = git_tree_entry_type(entry); | |
if (GIT_OBJ_TREE == entry_type) | |
{ | |
walk_data sub_payload({ full_name, data->repo }); | |
// | |
git_tree* sub_tree = nullptr; | |
if (git_error_info(git_tree_lookup(&sub_tree, sub_payload.repo, git_tree_entry_id(entry)))) | |
{ | |
return -1; | |
} | |
const auto result = git_error_info(git_tree_walk(sub_tree, GIT_TREEWALK_PRE, walk_cb, &sub_payload)); | |
git_tree_free(sub_tree); | |
sub_tree = nullptr; | |
if (result) | |
{ | |
return -1; | |
} | |
} | |
else if (GIT_OBJ_BLOB == entry_type) | |
{ | |
git_object* obj = nullptr; | |
if (git_error_info(git_tree_entry_to_object(&obj, data->repo, entry))) | |
{ | |
return -1; | |
} | |
const auto blob_size = get_blob_content(reinterpret_cast<git_blob*>(obj), walk_data::content); | |
// | |
git_object_free(obj); | |
obj = nullptr; | |
if (0 < blob_size) | |
{ | |
if (!data->real_root.empty()) | |
{ | |
if (!create_a_directory(data->real_root)) | |
{ | |
std::cerr << "[Warning]: Unable to create directory '"; | |
std::cerr << data->real_root; | |
std::cerr << "'." << std::endl; | |
} | |
} | |
if (!write_to_file(full_name, walk_data::content)) | |
{ | |
std::cerr << "[Warning]: Unable write to file '"; | |
std::cerr << full_name; | |
std::cerr << "'." << std::endl; | |
} | |
} | |
} | |
return 1; | |
} | |
bool save_files_from_revision( | |
const char* path_to_repository, | |
const char* commit_sha, | |
const char* output_directory) | |
{ | |
git_oid oid; | |
if (git_error_info(git_oid_fromstr(&oid, commit_sha))) | |
{ | |
return false; | |
} | |
git_repository* repo = nullptr; | |
if (git_error_info(git_repository_open(&repo, path_to_repository))) | |
{ | |
return false; | |
} | |
git_commit* commit = nullptr; | |
if (git_error_info(git_commit_lookup(&commit, repo, &oid))) | |
{ | |
git_repository_free(repo); | |
repo = nullptr; | |
// | |
return false; | |
} | |
const auto summary = git_commit_summary(commit); | |
const auto time = git_commit_time(commit); | |
#if defined(__STDC_LIB_EXT1__) || (defined(_MSC_VER) && (_MSC_VER >= 1400)) | |
std::string str_time(32, '\0'); | |
if (0 != ctime_s(&str_time.front(), str_time.size(), &time)) | |
{ | |
str_time.clear(); | |
} | |
#else | |
auto str_time = std::string(std::ctime(&time)); | |
#endif | |
if (!str_time.empty()) | |
{ | |
const auto pos = str_time.find('\n'); | |
if (std::string::npos != pos) | |
{ | |
str_time.resize(pos); | |
} | |
} | |
const auto committer = git_commit_committer(commit); | |
const auto author = git_commit_author(commit); | |
// | |
std::cout << "Commit : '"; | |
std::cout << commit_sha << "'" << std::endl; | |
std::cout << "Author : '"; | |
std::cout << author->name << "'" << std::endl; | |
std::cout << "Committer : '"; | |
std::cout << committer->name << "'" << std::endl; | |
std::cout << "Date : '"; | |
std::cout << str_time << "'" << std::endl; | |
std::cout << "Summary : '"; | |
std::cout << summary << "'" << std::endl; | |
// | |
git_tree* tree = nullptr; | |
if (git_error_info(git_commit_tree(&tree, commit))) | |
{ | |
git_commit_free(commit); | |
commit = nullptr; | |
// | |
git_repository_free(repo); | |
repo = nullptr; | |
// | |
return false; | |
} | |
walk_data payload({ std::string(output_directory) + "/" + commit_sha, repo }); | |
const auto result = git_error_info(git_tree_walk(tree, GIT_TREEWALK_PRE, walk_cb, &payload)); | |
// | |
git_tree_free(tree); | |
tree = nullptr; | |
// | |
git_commit_free(commit); | |
commit = nullptr; | |
// | |
git_repository_free(repo); | |
repo = nullptr; | |
// | |
return !result; | |
} | |
int main(int argc, char** argv) | |
{ | |
if (argc != 4 || git_error_info(git_libgit2_init())) | |
{ | |
return EXIT_FAILURE; | |
} | |
const auto result = save_files_from_revision(argv[1], argv[2], argv[3]); | |
if (git_error_info(git_libgit2_shutdown()) || !result) | |
{ | |
return EXIT_FAILURE; | |
} | |
return EXIT_SUCCESS; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* The MIT License (MIT) | |
* | |
* Copyright (c) 2019 https://github.com/TheVice/ | |
* | |
*/ | |
using System; | |
using System.IO; | |
using LibGit2Sharp; | |
namespace GitExtractor | |
{ | |
public class GitExtractor | |
{ | |
static long get_blob_content(Blob blob, Stream content) | |
{ | |
var blob_size = blob.Size; | |
if (0 < blob_size) | |
{ | |
blob.GetContentStream().CopyTo(content); | |
} | |
else | |
{ | |
blob_size = 0; | |
} | |
return blob_size; | |
} | |
static void walk(Tree tree, string real_root) | |
{ | |
foreach (var element in tree) | |
{ | |
var full_name = string.IsNullOrEmpty(real_root) ? element.Name : Path.Combine(real_root, element.Name); | |
var element_target = element.Target; | |
if (TreeEntryTargetType.Tree == element.TargetType) | |
{ | |
walk(element_target as Tree, full_name); | |
} | |
else if (TreeEntryTargetType.Blob == element.TargetType) | |
{ | |
if (!string.IsNullOrEmpty(real_root)) | |
{ | |
Directory.CreateDirectory(real_root); | |
} | |
using (var fs = new FileStream(full_name, FileMode.Create)) | |
{ | |
get_blob_content(element_target as Blob, fs); | |
} | |
} | |
} | |
} | |
static bool save_files_from_revision( | |
string path_to_repository, | |
string commit_sha, | |
string output_directory) | |
{ | |
var oid = new ObjectId(commit_sha); | |
var repository = new Repository(path_to_repository); | |
var commit = repository.Lookup(oid); | |
if (!(commit is Commit)) | |
{ | |
return false; | |
} | |
var real_commit = commit as Commit; | |
Console.WriteLine($"Commit : '{commit_sha}'"); | |
Console.WriteLine($"Author : '{real_commit.Author.Name}'"); | |
Console.WriteLine($"Committer : '{real_commit.Committer.Name}'"); | |
//"Date : '{}'"; | |
Console.WriteLine($"Summary : '{real_commit.MessageShort}'"); | |
walk(real_commit.Tree, Path.Combine(output_directory, commit_sha)); | |
return true; | |
} | |
#if !NETSTANDARD2_0 | |
public static void Main(string[] args) | |
{ | |
if (3 == args.Length) | |
{ | |
save_files_from_revision(args[0], args[1], args[2]); | |
} | |
} | |
#endif | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Project Sdk="Microsoft.NET.Sdk"> | |
<PropertyGroup> | |
<TargetFrameworks>netstandard2.0;net472</TargetFrameworks> | |
</PropertyGroup> | |
<PropertyGroup Condition=" '$(TargetFramework)' != 'netstandard2.0' "> | |
<OutputType>exe</OutputType> | |
</PropertyGroup> | |
<ItemGroup> | |
<PackageReference Include="LibGit2Sharp" Version="0.25.4" /> | |
</ItemGroup> | |
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# The MIT License (MIT) | |
# | |
# Copyright (c) 2019 https://github.com/TheVice/ | |
# | |
import io | |
from os import path, makedirs | |
from time import ctime | |
from pygit2 import Oid, Repository, Commit, Blob, Tree | |
def walk(repo, tree, root): | |
for treeElement in tree: | |
full_path = path.join(root, treeElement.name) | |
element = repo[treeElement.id] | |
if 'tree' == treeElement.type: | |
walk(repo, element, full_path) | |
elif 'blob' == treeElement.type: | |
if not path.exists(root): | |
makedirs(root) | |
file = open(full_path, 'wb') | |
file.write(element.data) | |
file.close() | |
def save_files_from_revision(path_to_repository, | |
commit_sha, | |
output_directory): | |
oid = Oid(hex=commit_sha) | |
repo = Repository(path=path_to_repository) | |
commit = repo[oid] | |
if isinstance(commit, Commit): | |
print("Commit : '" + commit_sha + "'") | |
print("Author : '" + commit.author.name + "'") | |
print("Committer : '" + commit.committer.name + "'") | |
print("Date : '" + ctime(commit.commit_time) + "'") | |
print("Summary : '" + | |
commit.message[:( | |
commit.message.find('\n') if -1 != commit.message.find('\n') else len(commit.message))] + "'") | |
walk(repo=repo, tree=commit.tree, root=path.join(output_directory, commit_sha)) | |
if __name__ == '__main__': | |
# import sys | |
import argparse | |
parser = argparse.ArgumentParser(description='Process input arguments.') | |
parser.add_argument('-path', '-path_to_repository', '--path', '--path_to_repository', required=True) | |
parser.add_argument('-sha', '-commit_sha', '--sha', '--commit_sha', required=True) | |
parser.add_argument('-output', '-output_directory', '--output', '--output_directory', required=True) | |
# args = vars(parser.parse_args(sys.argv)) | |
args = vars(parser.parse_args()) | |
# print(args['path'] + ' ' + args['sha'] + ' ' + args['output']) | |
save_files_from_revision(args['path'], args['sha'], args['output']) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment