From f5d36f0008fd1aa7494a6e91cc4c775c6656a9c8 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Sat, 7 Feb 2015 08:52:07 -0800 Subject: [PATCH 01/34] Fix wording of 'git-crypt lock' error message. Thanks to Elliot Saba. --- commands.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands.cpp b/commands.cpp index ef40b21..d859560 100644 --- a/commands.cpp +++ b/commands.cpp @@ -963,7 +963,7 @@ int lock (int argc, const char** argv) // just handle the given key std::string internal_key_path(get_internal_key_path(key_name)); if (access(internal_key_path.c_str(), F_OK) == -1 && errno == ENOENT) { - std::clog << "Error: this repository is not currently locked"; + std::clog << "Error: this repository is already locked"; if (key_name) { std::clog << " with key '" << key_name << "'"; } From c850d65242875a222e4f062b7e1d746a9416cd47 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Sat, 7 Feb 2015 12:15:53 -0800 Subject: [PATCH 02/34] Change "unconfigure" to "deconfigure" --- commands.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/commands.cpp b/commands.cpp index d859560..78674bb 100644 --- a/commands.cpp +++ b/commands.cpp @@ -73,7 +73,7 @@ static void git_config (const std::string& name, const std::string& value) } } -static void git_unconfig (const std::string& name) +static void git_deconfig (const std::string& name) { std::vector command; command.push_back("git"); @@ -107,11 +107,11 @@ static void configure_git_filters (const char* key_name) } } -static void unconfigure_git_filters (const char* key_name) +static void deconfigure_git_filters (const char* key_name) { - // unconfigure the git-crypt filters - git_unconfig("filter." + attribute_name(key_name)); - git_unconfig("diff." + attribute_name(key_name)); + // deconfigure the git-crypt filters + git_deconfig("filter." + attribute_name(key_name)); + git_deconfig("diff." + attribute_name(key_name)); } static bool git_checkout (const std::vector& paths) @@ -947,16 +947,16 @@ int lock (int argc, const char** argv) // mucked with the git config.) std::string path_to_top(get_path_to_top()); - // 3. unconfigure the git filters and remove decrypted keys + // 3. deconfigure the git filters and remove decrypted keys std::vector encrypted_files; if (all_keys) { - // unconfigure for all keys + // deconfigure for all keys std::vector dirents = get_directory_contents(get_internal_keys_path().c_str()); for (std::vector::const_iterator dirent(dirents.begin()); dirent != dirents.end(); ++dirent) { const char* this_key_name = (*dirent == "default" ? 0 : dirent->c_str()); remove_file(get_internal_key_path(this_key_name)); - unconfigure_git_filters(this_key_name); + deconfigure_git_filters(this_key_name); get_encrypted_files(encrypted_files, this_key_name); } } else { @@ -972,7 +972,7 @@ int lock (int argc, const char** argv) } remove_file(internal_key_path); - unconfigure_git_filters(key_name); + deconfigure_git_filters(key_name); get_encrypted_files(encrypted_files, key_name); } From fc583c7d4f91d03e1950a12ea7dfdbe9745b5ca2 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Sat, 7 Feb 2015 12:43:17 -0800 Subject: [PATCH 03/34] Add helper to get exit status of command --- util-unix.cpp | 4 ++-- util-win32.cpp | 4 ++-- util.hpp | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/util-unix.cpp b/util-unix.cpp index 1cebf3f..b1de1a1 100644 --- a/util-unix.cpp +++ b/util-unix.cpp @@ -273,9 +273,9 @@ int exec_command_with_input (const std::vector& command, const char return status; } -bool successful_exit (int status) +int exit_status (int wait_status) { - return status != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0; + return wait_status != -1 && WIFEXITED(wait_status) ? WEXITSTATUS(wait_status) : -1; } void touch_file (const std::string& filename) diff --git a/util-win32.cpp b/util-win32.cpp index 21576c7..7d9edf1 100644 --- a/util-win32.cpp +++ b/util-win32.cpp @@ -316,9 +316,9 @@ int exec_command_with_input (const std::vector& command, const char return exit_code; } -bool successful_exit (int status) +int exit_status (int status) { - return status == 0; + return status; } void touch_file (const std::string& filename) diff --git a/util.hpp b/util.hpp index 8b5bc33..1b044dc 100644 --- a/util.hpp +++ b/util.hpp @@ -63,7 +63,8 @@ std::string our_exe_path (); int exec_command (const std::vector&); int exec_command (const std::vector&, std::ostream& output); int exec_command_with_input (const std::vector&, const char* p, size_t len); -bool successful_exit (int status); +int exit_status (int wait_status); // returns -1 if process did not exit (but was signaled, etc.) +inline bool successful_exit (int wait_status) { return exit_status(wait_status) == 0; } void touch_file (const std::string&); void remove_file (const std::string&); std::string escape_shell_arg (const std::string&); From 0c8dae23755d31ff9a19137ec8049804fc652444 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Sat, 7 Feb 2015 12:53:54 -0800 Subject: [PATCH 04/34] Only run git_deconfig if Git configuration exists This will let us run 'git lock' even if no filters are configured. This logic is more complicated than I would like because running 'git config --remove-section' on a non-existent section results in a noisy error (with text printed to stderr and an exit code of 128) instead of a quiet error like the other 'git config' commands. --- commands.cpp | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/commands.cpp b/commands.cpp index 78674bb..fc44021 100644 --- a/commands.cpp +++ b/commands.cpp @@ -73,6 +73,22 @@ static void git_config (const std::string& name, const std::string& value) } } +static bool git_has_config (const std::string& name) +{ + std::vector command; + command.push_back("git"); + command.push_back("config"); + command.push_back("--get-all"); + command.push_back(name); + + std::stringstream output; + switch (exit_status(exec_command(command, output))) { + case 0: return true; + case 1: return false; + default: throw Error("'git config' failed"); + } +} + static void git_deconfig (const std::string& name) { std::vector command; @@ -110,8 +126,16 @@ static void configure_git_filters (const char* key_name) static void deconfigure_git_filters (const char* key_name) { // deconfigure the git-crypt filters - git_deconfig("filter." + attribute_name(key_name)); - git_deconfig("diff." + attribute_name(key_name)); + if (git_has_config("filter." + attribute_name(key_name) + ".smudge") || + git_has_config("filter." + attribute_name(key_name) + ".clean") || + git_has_config("filter." + attribute_name(key_name) + ".required")) { + + git_deconfig("filter." + attribute_name(key_name)); + } + + if (git_has_config("diff." + attribute_name(key_name) + ".textconv")) { + git_deconfig("diff." + attribute_name(key_name)); + } } static bool git_checkout (const std::vector& paths) From 18d3cfeca9bbcc3a51fcdc7d4dc607ae0edcf323 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Sat, 7 Feb 2015 12:57:52 -0800 Subject: [PATCH 05/34] Remove some dead code --- commands.cpp | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/commands.cpp b/commands.cpp index fc44021..51c0c43 100644 --- a/commands.cpp +++ b/commands.cpp @@ -837,12 +837,7 @@ int unlock (int argc, const char** argv) return 1; } - // 2. Determine the path to the top of the repository. We pass this as the argument - // to 'git checkout' below. (Determine the path now so in case it fails we haven't already - // mucked with the git config.) - std::string path_to_top(get_path_to_top()); - - // 3. Load the key(s) + // 2. Load the key(s) std::vector key_files; if (argc > 0) { // Read from the symmetric key file(s) @@ -890,7 +885,7 @@ int unlock (int argc, const char** argv) } - // 4. Install the key(s) and configure the git filters + // 3. Install the key(s) and configure the git filters std::vector encrypted_files; for (std::vector::iterator key_file(key_files.begin()); key_file != key_files.end(); ++key_file) { std::string internal_key_path(get_internal_key_path(key_file->get_key_name())); @@ -905,7 +900,7 @@ int unlock (int argc, const char** argv) get_encrypted_files(encrypted_files, key_file->get_key_name()); } - // 5. Check out the files that are currently encrypted. + // 4. Check out the files that are currently encrypted. // Git won't check out a file if its mtime hasn't changed, so touch every file first. for (std::vector::const_iterator file(encrypted_files.begin()); file != encrypted_files.end(); ++file) { touch_file(*file); @@ -966,12 +961,7 @@ int lock (int argc, const char** argv) return 1; } - // 2. Determine the path to the top of the repository. We pass this as the argument - // to 'git checkout' below. (Determine the path now so in case it fails we haven't already - // mucked with the git config.) - std::string path_to_top(get_path_to_top()); - - // 3. deconfigure the git filters and remove decrypted keys + // 2. deconfigure the git filters and remove decrypted keys std::vector encrypted_files; if (all_keys) { // deconfigure for all keys @@ -1000,7 +990,7 @@ int lock (int argc, const char** argv) get_encrypted_files(encrypted_files, key_name); } - // 4. Check out the files that are currently decrypted but should be encrypted. + // 3. Check out the files that are currently decrypted but should be encrypted. // Git won't check out a file if its mtime hasn't changed, so touch every file first. for (std::vector::const_iterator file(encrypted_files.begin()); file != encrypted_files.end(); ++file) { touch_file(*file); From 85635ae0b1b90a25c062eaf14f77550cfe4d3eb5 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Sat, 7 Feb 2015 13:22:30 -0800 Subject: [PATCH 06/34] touch_file, remove_file: ignore non-existent files --- util-unix.cpp | 4 ++-- util-win32.cpp | 14 ++++++++++++-- util.hpp | 4 ++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/util-unix.cpp b/util-unix.cpp index b1de1a1..90e215c 100644 --- a/util-unix.cpp +++ b/util-unix.cpp @@ -280,14 +280,14 @@ int exit_status (int wait_status) void touch_file (const std::string& filename) { - if (utimes(filename.c_str(), NULL) == -1) { + if (utimes(filename.c_str(), NULL) == -1 && errno != ENOENT) { throw System_error("utimes", filename, errno); } } void remove_file (const std::string& filename) { - if (unlink(filename.c_str()) == -1) { + if (unlink(filename.c_str()) == -1 && errno != ENOENT) { throw System_error("unlink", filename, errno); } } diff --git a/util-win32.cpp b/util-win32.cpp index 7d9edf1..ceda202 100644 --- a/util-win32.cpp +++ b/util-win32.cpp @@ -325,7 +325,12 @@ void touch_file (const std::string& filename) { HANDLE fh = CreateFileA(filename.c_str(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (fh == INVALID_HANDLE_VALUE) { - throw System_error("CreateFileA", filename, GetLastError()); + DWORD error = GetLastError(); + if (error == ERROR_FILE_NOT_FOUND) { + return; + } else { + throw System_error("CreateFileA", filename, error); + } } SYSTEMTIME system_time; GetSystemTime(&system_time); @@ -343,7 +348,12 @@ void touch_file (const std::string& filename) void remove_file (const std::string& filename) { if (!DeleteFileA(filename.c_str())) { - throw System_error("DeleteFileA", filename, GetLastError()); + DWORD error = GetLastError(); + if (error == ERROR_FILE_NOT_FOUND) { + return; + } else { + throw System_error("DeleteFileA", filename, error); + } } } diff --git a/util.hpp b/util.hpp index 1b044dc..2a9d77a 100644 --- a/util.hpp +++ b/util.hpp @@ -65,8 +65,8 @@ int exec_command (const std::vector&, std::ostream& output); int exec_command_with_input (const std::vector&, const char* p, size_t len); int exit_status (int wait_status); // returns -1 if process did not exit (but was signaled, etc.) inline bool successful_exit (int wait_status) { return exit_status(wait_status) == 0; } -void touch_file (const std::string&); -void remove_file (const std::string&); +void touch_file (const std::string&); // ignores non-existent files +void remove_file (const std::string&); // ignores non-existent files std::string escape_shell_arg (const std::string&); uint32_t load_be32 (const unsigned char*); void store_be32 (unsigned char*, uint32_t); From c2de1e21948d8ac359e3a4bad7f73f3c00292834 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Sat, 7 Feb 2015 13:27:58 -0800 Subject: [PATCH 07/34] Add --force option to 'git-crypt lock' It will force a lock even if working directory is unclean. Useful for deconfiguring git-crypt if you've accidentally unlocked with the wrong key or gotten into a similarly sticky situation. --- commands.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/commands.cpp b/commands.cpp index 51c0c43..342ba15 100644 --- a/commands.cpp +++ b/commands.cpp @@ -919,19 +919,23 @@ void help_lock (std::ostream& out) // |--------------------------------------------------------------------------------| 80 chars out << "Usage: git-crypt lock [OPTIONS]" << std::endl; out << std::endl; - out << " -a, --all Lock all keys, instead of just the default" << std::endl; - out << " -k, --key-name KEYNAME Lock the given key, instead of the default" << std::endl; + out << " -a, --all Lock all keys, instead of just the default" << std::endl; + out << " -k, --key-name KEYNAME Lock the given key, instead of the default" << std::endl; + out << " -f, --force Lock even if unclean (you may lose uncommited work)" << std::endl; out << std::endl; } int lock (int argc, const char** argv) { const char* key_name = 0; - bool all_keys = false; + bool all_keys = false; + bool force = false; Options_list options; options.push_back(Option_def("-k", &key_name)); options.push_back(Option_def("--key-name", &key_name)); options.push_back(Option_def("-a", &all_keys)); options.push_back(Option_def("--all", &all_keys)); + options.push_back(Option_def("-f", &force)); + options.push_back(Option_def("--force", &force)); int argi = parse_options(options, argc, argv); @@ -955,9 +959,10 @@ int lock (int argc, const char** argv) std::stringstream status_output; get_git_status(status_output); - if (status_output.peek() != -1) { + if (!force && status_output.peek() != -1) { std::clog << "Error: Working directory not clean." << std::endl; std::clog << "Please commit your changes or 'git stash' them before running 'git-crypt lock'." << std::endl; + std::clog << "Or, use 'git-crypt lock --force' and possibly lose uncommitted changes." << std::endl; return 1; } From 99d4408f9ed20e00b6927acc8dc1dcce9075d756 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Mon, 9 Feb 2015 11:37:19 -0800 Subject: [PATCH 08/34] README: Add note about GitHub for Mac breakage --- README | 6 +++--- README.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README b/README index 2d90b29..2f86e7c 100644 --- a/README +++ b/README @@ -116,9 +116,9 @@ the patch itself is encrypted. To generate an encrypted patch, use `git diff --no-textconv --binary`. Alternatively, you can apply a plaintext patch outside of git using the patch command. -git-crypt does not work reliably with Atlassian SourceTree. -Files might be left in an unencrypted state. See -. +git-crypt does not work reliably with some third-party git GUIs, such +as Atlassian SourceTree +and GitHub for Mac. Files might be left in an unencrypted state. GITATTRIBUTES FILE diff --git a/README.md b/README.md index c5759c4..e1f15e6 100644 --- a/README.md +++ b/README.md @@ -118,9 +118,9 @@ the patch itself is encrypted. To generate an encrypted patch, use `git diff --no-textconv --binary`. Alternatively, you can apply a plaintext patch outside of git using the patch command. -git-crypt does [not work reliably with Atlassian -SourceTree](https://jira.atlassian.com/browse/SRCTREE-2511). Files might -be left in an unencrypted state. +git-crypt does not work reliably with some third-party git GUIs, such +as [Atlassian SourceTree](https://jira.atlassian.com/browse/SRCTREE-2511) +and GitHub for Mac. Files might be left in an unencrypted state. Gitattributes File ------------------ From 012d78e1fef83d586ec791b5dc8bacf479b971a5 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Mon, 9 Feb 2015 11:37:50 -0800 Subject: [PATCH 09/34] Add "do not edit" comment to .git-crypt/.gitattributes file --- commands.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/commands.cpp b/commands.cpp index 342ba15..c9a6165 100644 --- a/commands.cpp +++ b/commands.cpp @@ -1069,6 +1069,9 @@ int add_gpg_user (int argc, const char** argv) const std::string state_gitattributes_path(state_path + "/.gitattributes"); if (access(state_gitattributes_path.c_str(), F_OK) != 0) { std::ofstream state_gitattributes_file(state_gitattributes_path.c_str()); + // |--------------------------------------------------------------------------------| 80 chars + state_gitattributes_file << "# Do not edit this file. To specify the files to encrypt, create your own\"; + state_gitattributes_file << "# .gitattributes file in the directory where your files are.\n"; state_gitattributes_file << "* !filter !diff\n"; state_gitattributes_file.close(); if (!state_gitattributes_file) { From 8c130d3a002aad72d97f36c93c5ef1de4739b295 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Mon, 9 Feb 2015 11:42:22 -0800 Subject: [PATCH 10/34] Fix syntax error in commands.cpp --- commands.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands.cpp b/commands.cpp index c9a6165..dbe2c56 100644 --- a/commands.cpp +++ b/commands.cpp @@ -1070,7 +1070,7 @@ int add_gpg_user (int argc, const char** argv) if (access(state_gitattributes_path.c_str(), F_OK) != 0) { std::ofstream state_gitattributes_file(state_gitattributes_path.c_str()); // |--------------------------------------------------------------------------------| 80 chars - state_gitattributes_file << "# Do not edit this file. To specify the files to encrypt, create your own\"; + state_gitattributes_file << "# Do not edit this file. To specify the files to encrypt, create your own\n"; state_gitattributes_file << "# .gitattributes file in the directory where your files are.\n"; state_gitattributes_file << "* !filter !diff\n"; state_gitattributes_file.close(); From 1b3f13643c2787c16c4408310c42356cc7e8ee00 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Thu, 26 Feb 2015 22:51:40 -0800 Subject: [PATCH 11/34] Use opendir/readdir instead of scandir scandir is a relatively recent addition to POSIX and is not available on older versions of Mac OS X. opendir/readdir should be more portable. --- util-unix.cpp | 52 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/util-unix.cpp b/util-unix.cpp index 90e215c..4eed56d 100644 --- a/util-unix.cpp +++ b/util-unix.cpp @@ -43,6 +43,8 @@ #include #include #include +#include +#include std::string System_error::message () const { @@ -310,25 +312,47 @@ int util_rename (const char* from, const char* to) return rename(from, to); } -static int dirfilter (const struct dirent* ent) +static size_t sizeof_dirent_for (DIR* p) { - // filter out . and .. - return std::strcmp(ent->d_name, ".") != 0 && std::strcmp(ent->d_name, "..") != 0; + long name_max = fpathconf(dirfd(p), _PC_NAME_MAX); + if (name_max == -1) { + #ifdef NAME_MAX + name_max = NAME_MAX; + #else + name_max = 255; + #endif + } + return offsetof(struct dirent, d_name) + name_max + 1; // final +1 is for d_name's null terminator } std::vector get_directory_contents (const char* path) { - struct dirent** namelist; - int n = scandir(path, &namelist, dirfilter, alphasort); - if (n == -1) { - throw System_error("scandir", path, errno); - } - std::vector contents(n); - for (int i = 0; i < n; ++i) { - contents[i] = namelist[i]->d_name; - free(namelist[i]); - } - free(namelist); + std::vector contents; + DIR* dir = opendir(path); + if (!dir) { + throw System_error("opendir", path, errno); + } + try { + std::vector buffer(sizeof_dirent_for(dir)); + struct dirent* dirent_buffer = reinterpret_cast(&buffer[0]); + struct dirent* ent = NULL; + int err = 0; + while ((err = readdir_r(dir, dirent_buffer, &ent)) == 0 && ent != NULL) { + if (std::strcmp(ent->d_name, ".") == 0 || std::strcmp(ent->d_name, "..") == 0) { + continue; + } + contents.push_back(ent->d_name); + } + if (err != 0) { + throw System_error("readdir_r", path, errno); + } + } catch (...) { + closedir(dir); + throw; + } + closedir(dir); + + std::sort(contents.begin(), contents.end()); return contents; } From ede1461563709498d397d8230d233c3d9a25880e Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Tue, 10 Mar 2015 08:29:06 -0700 Subject: [PATCH 12/34] Makefile: create destination directories in make install --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 463b502..b53cfb1 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,7 @@ clean: rm -f *.o git-crypt install: git-crypt + install -d $(DESTDIR)$(PREFIX)/bin/ install -m 755 git-crypt $(DESTDIR)$(PREFIX)/bin/ .PHONY: all clean install From 6abf9c6956674de5a0efd44d702220a1315a0594 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Sun, 15 Mar 2015 13:34:31 -0700 Subject: [PATCH 13/34] Ignore non-files when running `git ls-files` Non-files (symlinks and gitlinks (used by sub-modules)) cannot be encrypted, so we shouldn't try messing with them. This fixes `git-crypt status` when used on a repository with sub-modules or symlinks when the path to the sub-module or symlink has the git-crypt attribute (which can happen inadvertently when using wildcards in .gitattributes). --- commands.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/commands.cpp b/commands.cpp index dbe2c56..4a53186 100644 --- a/commands.cpp +++ b/commands.cpp @@ -380,13 +380,18 @@ static bool check_if_file_is_encrypted (const std::string& filename) return check_if_blob_is_encrypted(object_id); } +static bool is_git_file_mode (const std::string& mode) +{ + return (std::strtoul(mode.c_str(), NULL, 8) & 0170000) == 0100000; +} + static void get_encrypted_files (std::vector& files, const char* key_name) { // git ls-files -cz -- path_to_top std::vector command; command.push_back("git"); command.push_back("ls-files"); - command.push_back("-cz"); + command.push_back("-csz"); command.push_back("--"); const std::string path_to_top(get_path_to_top()); if (!path_to_top.empty()) { @@ -399,11 +404,15 @@ static void get_encrypted_files (std::vector& files, const char* ke } while (output.peek() != -1) { + std::string mode; + std::string object_id; + std::string stage; std::string filename; + output >> mode >> object_id >> stage >> std::ws; std::getline(output, filename, '\0'); // TODO: get file attributes en masse for efficiency... unfortunately this requires machine-parseable output from git check-attr to be workable, and this is only supported in Git 1.8.5 and above (released 27 Nov 2013) - if (get_file_attributes(filename).first == attribute_name(key_name)) { + if (is_git_file_mode(mode) && get_file_attributes(filename).first == attribute_name(key_name)) { files.push_back(filename); } } @@ -1420,6 +1429,9 @@ int status (int argc, const char** argv) std::string mode; std::string stage; output >> mode >> object_id >> stage; + if (!is_git_file_mode(mode)) { + continue; + } } output >> std::ws; std::getline(output, filename, '\0'); From c3ab393f17afea50aae732d4d26044e0b5dd65f7 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Sun, 15 Mar 2015 13:55:18 -0700 Subject: [PATCH 14/34] Add notes to the README about symlinks, sub-modules --- README | 6 ++++-- README.md | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/README b/README index 2f86e7c..46ed750 100644 --- a/README +++ b/README @@ -33,7 +33,8 @@ Specify files to encrypt by creating a .gitattributes file: Like a .gitignore file, it can match wildcards and should be checked into the repository. See below for more information about .gitattributes. -Make sure you don't accidentally encrypt the .gitattributes file itself! +Make sure you don't accidentally encrypt the .gitattributes file itself +(or other git files like .gitignore or .gitmodules). Share the repository with others (or with yourself) using GPG: @@ -98,7 +99,8 @@ need to encrypt. For encrypting an entire repository, consider using a system like git-remote-gcrypt instead. (Note: no endorsement is made of git-remote-gcrypt's security.) -git-crypt does not encrypt file names, commit messages, or other metadata. +git-crypt does not encrypt file names, commit messages, symlink targets, +gitlinks, or other metadata. Files encrypted with git-crypt are not compressible. Even the smallest change to an encrypted file requires git to store the entire changed file, diff --git a/README.md b/README.md index e1f15e6..785b494 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,8 @@ Specify files to encrypt by creating a .gitattributes file: Like a .gitignore file, it can match wildcards and should be checked into the repository. See below for more information about .gitattributes. -Make sure you don't accidentally encrypt the .gitattributes file itself! +Make sure you don't accidentally encrypt the .gitattributes file itself +(or other git files like .gitignore or .gitmodules). Share the repository with others (or with yourself) using GPG: @@ -100,7 +101,8 @@ need to encrypt. For encrypting an entire repository, consider using a system like [git-remote-gcrypt](https://github.com/joeyh/git-remote-gcrypt) instead. (Note: no endorsement is made of git-remote-gcrypt's security.) -git-crypt does not encrypt file names, commit messages, or other metadata. +git-crypt does not encrypt file names, commit messages, symlink targets, +gitlinks, or other metadata. Files encrypted with git-crypt are not compressible. Even the smallest change to an encrypted file requires git to store the entire changed file, From 6d6e96f82b63cc09274367668de90d62dc027a32 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Tue, 17 Mar 2015 15:08:35 -0700 Subject: [PATCH 15/34] Clarify security section in README --- README | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README b/README index 46ed750..2eae0fe 100644 --- a/README +++ b/README @@ -79,7 +79,7 @@ SECURITY git-crypt is more secure that other transparent git encryption systems. git-crypt encrypts files using AES-256 in CTR mode with a synthetic IV -derived from the SHA-1 HMAC of the file. This is provably semantically +derived from the SHA-1 HMAC of the file. This mode of operation is provably semantically secure under deterministic chosen-plaintext attack. That means that although the encryption is deterministic (which is required so git can distinguish when a file has and hasn't changed), it leaks no information diff --git a/README.md b/README.md index 785b494..058f6ea 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ Security git-crypt is more secure that other transparent git encryption systems. git-crypt encrypts files using AES-256 in CTR mode with a synthetic IV -derived from the SHA-1 HMAC of the file. This is provably semantically +derived from the SHA-1 HMAC of the file. This mode of operation is provably semantically secure under deterministic chosen-plaintext attack. That means that although the encryption is deterministic (which is required so git can distinguish when a file has and hasn't changed), it leaks no information From 3ce5c83b2d70c899cf27ff5585f3826ce4826f43 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Tue, 17 Mar 2015 15:08:58 -0700 Subject: [PATCH 16/34] Wrap long lines in README --- README | 14 +++++++------- README.md | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README b/README index 2eae0fe..5e24bc7 100644 --- a/README +++ b/README @@ -79,13 +79,13 @@ SECURITY git-crypt is more secure that other transparent git encryption systems. git-crypt encrypts files using AES-256 in CTR mode with a synthetic IV -derived from the SHA-1 HMAC of the file. This mode of operation is provably semantically -secure under deterministic chosen-plaintext attack. That means that -although the encryption is deterministic (which is required so git can -distinguish when a file has and hasn't changed), it leaks no information -beyond whether two files are identical or not. Other proposals for -transparent git encryption use ECB or CBC with a fixed IV. These systems -are not semantically secure and leak information. +derived from the SHA-1 HMAC of the file. This mode of operation is +provably semantically secure under deterministic chosen-plaintext attack. +That means that although the encryption is deterministic (which is +required so git can distinguish when a file has and hasn't changed), +it leaks no information beyond whether two files are identical or not. +Other proposals for transparent git encryption use ECB or CBC with a +fixed IV. These systems are not semantically secure and leak information. LIMITATIONS diff --git a/README.md b/README.md index 058f6ea..5958a7c 100644 --- a/README.md +++ b/README.md @@ -81,13 +81,13 @@ Security git-crypt is more secure that other transparent git encryption systems. git-crypt encrypts files using AES-256 in CTR mode with a synthetic IV -derived from the SHA-1 HMAC of the file. This mode of operation is provably semantically -secure under deterministic chosen-plaintext attack. That means that -although the encryption is deterministic (which is required so git can -distinguish when a file has and hasn't changed), it leaks no information -beyond whether two files are identical or not. Other proposals for -transparent git encryption use ECB or CBC with a fixed IV. These -systems are not semantically secure and leak information. +derived from the SHA-1 HMAC of the file. This mode of operation is +provably semantically secure under deterministic chosen-plaintext attack. +That means that although the encryption is deterministic (which is +required so git can distinguish when a file has and hasn't changed), +it leaks no information beyond whether two files are identical or not. +Other proposals for transparent git encryption use ECB or CBC with a +fixed IV. These systems are not semantically secure and leak information. Limitations ----------- From ad71c7ffaeb96107ad3dfa59af2b93da248fb257 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Tue, 31 Mar 2015 19:41:19 -0700 Subject: [PATCH 17/34] FIx GPG key lookup with with-fingerprint enabled in gpg.conf When the with-fingerprint option is enabled, the gpg command invoked by git-crypt to look up a GPG user ID returns a fingerprint for both primary keys and sub-keys. Previously, this misled git-crypt into thinking that the user ID matched more than one public key. Now, git-crypt ignores fingerprints for sub-keys. --- gpg.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/gpg.cpp b/gpg.cpp index 4813b35..2cbb06a 100644 --- a/gpg.cpp +++ b/gpg.cpp @@ -102,10 +102,15 @@ std::vector gpg_lookup_key (const std::string& query) command.push_back(query); std::stringstream command_output; if (successful_exit(exec_command(command, command_output))) { + bool is_pubkey = false; while (command_output.peek() != -1) { std::string line; std::getline(command_output, line); - if (line.substr(0, 4) == "fpr:") { + if (line.substr(0, 4) == "pub:") { + is_pubkey = true; + } else if (line.substr(0, 4) == "sub:") { + is_pubkey = false; + } else if (is_pubkey && line.substr(0, 4) == "fpr:") { // fpr:::::::::7A399B2DB06D039020CD1CE1D0F3702D61489532: // want the 9th column (counting from 0) fingerprints.push_back(gpg_nth_column(line, 9)); From 3104508adf1ffd7a8565bd5e7e06dda546ee10a9 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Tue, 31 Mar 2015 20:24:13 -0700 Subject: [PATCH 18/34] Add --trusted option to gpg-add-user If this option is specified, then the GPG users are added even if their keys are not trusted by GPG. In addition, if a full fingerprint, prefixed by 0x, is specified, it is assumed to be trusted, regardless of its trust level in the GPG trustdb. --- commands.cpp | 25 ++++++++++++++++--------- gpg.cpp | 6 +++++- gpg.hpp | 2 +- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/commands.cpp b/commands.cpp index 4a53186..b23f185 100644 --- a/commands.cpp +++ b/commands.cpp @@ -495,7 +495,7 @@ static bool decrypt_repo_keys (std::vector& key_files, uint32_t key_ve return successful; } -static void encrypt_repo_key (const char* key_name, const Key_file::Entry& key, const std::vector& collab_keys, const std::string& keys_path, std::vector* new_files) +static void encrypt_repo_key (const char* key_name, const Key_file::Entry& key, const std::vector >& collab_keys, const std::string& keys_path, std::vector* new_files) { std::string key_file_data; { @@ -505,9 +505,11 @@ static void encrypt_repo_key (const char* key_name, const Key_file::Entry& key, key_file_data = this_version_key_file.store_to_string(); } - for (std::vector::const_iterator collab(collab_keys.begin()); collab != collab_keys.end(); ++collab) { + for (std::vector >::const_iterator collab(collab_keys.begin()); collab != collab_keys.end(); ++collab) { + const std::string& fingerprint(collab->first); + const bool key_is_trusted(collab->second); std::ostringstream path_builder; - path_builder << keys_path << '/' << (key_name ? key_name : "default") << '/' << key.version << '/' << *collab << ".gpg"; + path_builder << keys_path << '/' << (key_name ? key_name : "default") << '/' << key.version << '/' << fingerprint << ".gpg"; std::string path(path_builder.str()); if (access(path.c_str(), F_OK) == 0) { @@ -515,7 +517,7 @@ static void encrypt_repo_key (const char* key_name, const Key_file::Entry& key, } mkdir_parent(path); - gpg_encrypt_to_file(path, *collab, key_file_data.data(), key_file_data.size()); + gpg_encrypt_to_file(path, fingerprint, key_is_trusted, key_file_data.data(), key_file_data.size()); new_files->push_back(path); } } @@ -1025,17 +1027,20 @@ void help_add_gpg_user (std::ostream& out) out << std::endl; out << " -k, --key-name KEYNAME Add GPG user to given key, instead of default" << std::endl; out << " -n, --no-commit Don't automatically commit" << std::endl; + out << " --trusted Assume the GPG user IDs are trusted" << std::endl; out << std::endl; } int add_gpg_user (int argc, const char** argv) { const char* key_name = 0; bool no_commit = false; + bool trusted = false; Options_list options; options.push_back(Option_def("-k", &key_name)); options.push_back(Option_def("--key-name", &key_name)); options.push_back(Option_def("-n", &no_commit)); options.push_back(Option_def("--no-commit", &no_commit)); + options.push_back(Option_def("--trusted", &trusted)); int argi = parse_options(options, argc, argv); if (argc - argi == 0) { @@ -1044,8 +1049,8 @@ int add_gpg_user (int argc, const char** argv) return 2; } - // build a list of key fingerprints for every collaborator specified on the command line - std::vector collab_keys; + // build a list of key fingerprints, and whether the key is trusted, for every collaborator specified on the command line + std::vector > collab_keys; for (int i = argi; i < argc; ++i) { std::vector keys(gpg_lookup_key(argv[i])); @@ -1057,7 +1062,9 @@ int add_gpg_user (int argc, const char** argv) std::clog << "Error: more than one public key matches '" << argv[i] << "' - please be more specific" << std::endl; return 1; } - collab_keys.push_back(keys[0]); + + const bool is_full_fingerprint(std::strncmp(argv[i], "0x", 2) == 0 && std::strlen(argv[i]) == 42); + collab_keys.push_back(std::make_pair(keys[0], trusted || is_full_fingerprint)); } // TODO: have a retroactive option to grant access to all key versions, not just the most recent @@ -1108,8 +1115,8 @@ int add_gpg_user (int argc, const char** argv) // TODO: include key_name in commit message std::ostringstream commit_message_builder; commit_message_builder << "Add " << collab_keys.size() << " git-crypt collaborator" << (collab_keys.size() != 1 ? "s" : "") << "\n\nNew collaborators:\n\n"; - for (std::vector::const_iterator collab(collab_keys.begin()); collab != collab_keys.end(); ++collab) { - commit_message_builder << '\t' << gpg_shorten_fingerprint(*collab) << ' ' << gpg_get_uid(*collab) << '\n'; + for (std::vector >::const_iterator collab(collab_keys.begin()); collab != collab_keys.end(); ++collab) { + commit_message_builder << '\t' << gpg_shorten_fingerprint(collab->first) << ' ' << gpg_get_uid(collab->first) << '\n'; } // git commit -m MESSAGE NEW_FILE ... diff --git a/gpg.cpp b/gpg.cpp index 2cbb06a..04f3f60 100644 --- a/gpg.cpp +++ b/gpg.cpp @@ -150,12 +150,16 @@ std::vector gpg_list_secret_keys () return secret_keys; } -void gpg_encrypt_to_file (const std::string& filename, const std::string& recipient_fingerprint, const char* p, size_t len) +void gpg_encrypt_to_file (const std::string& filename, const std::string& recipient_fingerprint, bool key_is_trusted, const char* p, size_t len) { // gpg --batch -o FILENAME -r RECIPIENT -e std::vector command; command.push_back("gpg"); command.push_back("--batch"); + if (key_is_trusted) { + command.push_back("--trust-model"); + command.push_back("always"); + } command.push_back("-o"); command.push_back(filename); command.push_back("-r"); diff --git a/gpg.hpp b/gpg.hpp index cd55171..77997b1 100644 --- a/gpg.hpp +++ b/gpg.hpp @@ -45,7 +45,7 @@ std::string gpg_shorten_fingerprint (const std::string& fingerprint); std::string gpg_get_uid (const std::string& fingerprint); std::vector gpg_lookup_key (const std::string& query); std::vector gpg_list_secret_keys (); -void gpg_encrypt_to_file (const std::string& filename, const std::string& recipient_fingerprint, const char* p, size_t len); +void gpg_encrypt_to_file (const std::string& filename, const std::string& recipient_fingerprint, bool key_is_trusted, const char* p, size_t len); void gpg_decrypt_from_file (const std::string& filename, std::ostream&); #endif From 7880b30e8c4950778d74cda8c9855b2fd90b17ae Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Fri, 24 Apr 2015 20:53:19 -0700 Subject: [PATCH 19/34] Update INSTALL file Streamline the instructions a bit. Mention the Debian/Ubuntu and RHEL/CentOS packages for each dependency. --- INSTALL | 29 ++++++++++++++++++++--------- INSTALL.md | 44 +++++++++++++++++++++++++------------------- 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/INSTALL b/INSTALL index 9c8e8e0..5df9b09 100644 --- a/INSTALL +++ b/INSTALL @@ -1,23 +1,34 @@ DEPENDENCIES +To build git-crypt, you need: + + Debian/Ubuntu package RHEL/CentOS package + ----------------------------------------------------------------------------- + Make make make + A C++ compiler (e.g. gcc) g++ gcc-c++ + OpenSSL development files libssl-dev openssl-devel + + To use git-crypt, you need: - * Git 1.7.2 or newer - * OpenSSL - -To build git-crypt, you need a C++ compiler and OpenSSL development -headers. + Debian/Ubuntu package RHEL/CentOS package + ----------------------------------------------------------------------------- + Git 1.7.2 or newer git git + OpenSSL openssl openssl BUILDING GIT-CRYPT -The Makefile is tailored for g++, but should work with other compilers. +Run: $ make - $ cp git-crypt /usr/local/bin/ + $ make install -It doesn't matter where you install the git-crypt binary - choose wherever -is most convenient for you. +To install to a specific location: + + $ make install PREFIX=/usr/local + +Or, just copy the git-crypt binary to wherever is most convenient for you. BUILDING A DEBIAN PACKAGE diff --git a/INSTALL.md b/INSTALL.md index b4ee879..b129565 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,29 +1,37 @@ -Dependencies ------------- +### Dependencies + +To build git-crypt, you need: + + | Debian/Ubuntu package | RHEL/CentOS package +---------------------------|-----------------------|------------------------ +Make | make | make +A C++ compiler (e.g. gcc) | g++ | gcc-c++ +OpenSSL development files | libssl-dev | openssl-devel + To use git-crypt, you need: -* Git 1.7.2 or newer -* OpenSSL - -To build git-crypt, you need a C++ compiler and OpenSSL development -headers. + | Debian/Ubuntu package | RHEL/CentOS package +---------------------------|-----------------------|------------------------ +Git 1.7.2 or newer | git | git +OpenSSL | openssl | openssl -Building git-crypt ------------------- +### Building git-crypt -The Makefile is tailored for g++, but should work with other compilers. +Run: make - cp git-crypt /usr/local/bin/ + make install -It doesn't matter where you install the git-crypt binary - choose -wherever is most convenient for you. +To install to a specific location: + + make install PREFIX=/usr/local + +Or, just copy the git-crypt binary to wherever is most convenient for you. -Building A Debian Package -------------------------- +### Building A Debian Package Debian packaging can be found in the 'debian' branch of the project Git repository. The package is built using git-buildpackage as follows: @@ -32,15 +40,13 @@ repository. The package is built using git-buildpackage as follows: git-buildpackage -uc -us -Installing On Mac OS X ----------------------- +### Installing On Mac OS X Using the brew package manager, simply run: brew install git-crypt -Experimental Windows Support ----------------------------- +### Experimental Windows Support git-crypt should build on Windows with MinGW, although the build system is not yet finalized so you will need to pass your own CXX, CXXFLAGS, and From 439bcd852d85819c9a541cb38a29389221f8788b Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Thu, 14 May 2015 22:23:21 -0700 Subject: [PATCH 20/34] Write a helper function to get the version of Git This will be useful as we start to gate code on the version of Git that's installed. A lot of code out in the wild seems to assume that the output of `git version` is "git version $VERSION", so I'm assuming it's safe for git-crypt to rely on that too. --- commands.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/commands.cpp b/commands.cpp index b23f185..f984132 100644 --- a/commands.cpp +++ b/commands.cpp @@ -60,6 +60,23 @@ static std::string attribute_name (const char* key_name) } } +static std::string git_version () +{ + std::vector command; + command.push_back("git"); + command.push_back("version"); + + std::stringstream output; + if (!successful_exit(exec_command(command, output))) { + throw Error("'git version' failed - is Git installed?"); + } + std::string word; + output >> word; // "git" + output >> word; // "version" + output >> word; // "1.7.10.4" + return word; +} + static void git_config (const std::string& name, const std::string& value) { std::vector command; From c279a6a20ade252d221853a89733e734ccb01fb2 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Sat, 16 May 2015 21:10:44 -0700 Subject: [PATCH 21/34] Add helpers to faciliate Git version comparison This will be useful as we start to gate code on the version of Git that's installed. --- commands.cpp | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/commands.cpp b/commands.cpp index f984132..f8e9f23 100644 --- a/commands.cpp +++ b/commands.cpp @@ -60,7 +60,7 @@ static std::string attribute_name (const char* key_name) } } -static std::string git_version () +static std::string git_version_string () { std::vector command; command.push_back("git"); @@ -77,6 +77,31 @@ static std::string git_version () return word; } +static std::vector parse_version (const std::string& str) +{ + std::istringstream in(str); + std::vector version; + std::string component; + while (std::getline(in, component, '.')) { + version.push_back(std::atoi(component.c_str())); + } + return version; +} + +static std::vector git_version () +{ + return parse_version(git_version_string()); +} + +static std::vector make_version (int a, int b, int c) +{ + std::vector version; + version.push_back(a); + version.push_back(b); + version.push_back(c); + return version; +} + static void git_config (const std::string& name, const std::string& value) { std::vector command; From 3db6271492ba705b8969a9dea8ede9aab2931ae9 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Sun, 17 May 2015 14:38:12 -0700 Subject: [PATCH 22/34] Re-license parse_options under X11 license --- parse_options.cpp | 41 +++++++++++++++++++---------------------- parse_options.hpp | 41 +++++++++++++++++++---------------------- 2 files changed, 38 insertions(+), 44 deletions(-) diff --git a/parse_options.cpp b/parse_options.cpp index 51b51f7..008e29d 100644 --- a/parse_options.cpp +++ b/parse_options.cpp @@ -1,31 +1,28 @@ /* * Copyright 2014 Andrew Ayer * - * This file is part of git-crypt. + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: * - * git-crypt is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. * - * git-crypt is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. * - * You should have received a copy of the GNU General Public License - * along with git-crypt. If not, see . - * - * Additional permission under GNU GPL version 3 section 7: - * - * If you modify the Program, or any covered work, by linking or - * combining it with the OpenSSL project's OpenSSL library (or a - * modified version of that library), containing parts covered by the - * terms of the OpenSSL or SSLeay licenses, the licensors of the Program - * grant you additional permission to convey the resulting work. - * Corresponding Source for a non-source form of such a combination - * shall include the source code for the parts of OpenSSL used as well - * as that of the covered work. + * Except as contained in this notice, the name(s) of the above copyright + * holders shall not be used in advertising or otherwise to promote the + * sale, use or other dealings in this Software without prior written + * authorization. */ #include "parse_options.hpp" diff --git a/parse_options.hpp b/parse_options.hpp index c0580f0..fa1ef87 100644 --- a/parse_options.hpp +++ b/parse_options.hpp @@ -1,31 +1,28 @@ /* * Copyright 2014 Andrew Ayer * - * This file is part of git-crypt. + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: * - * git-crypt is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. * - * git-crypt is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. * - * You should have received a copy of the GNU General Public License - * along with git-crypt. If not, see . - * - * Additional permission under GNU GPL version 3 section 7: - * - * If you modify the Program, or any covered work, by linking or - * combining it with the OpenSSL project's OpenSSL library (or a - * modified version of that library), containing parts covered by the - * terms of the OpenSSL or SSLeay licenses, the licensors of the Program - * grant you additional permission to convey the resulting work. - * Corresponding Source for a non-source form of such a combination - * shall include the source code for the parts of OpenSSL used as well - * as that of the covered work. + * Except as contained in this notice, the name(s) of the above copyright + * holders shall not be used in advertising or otherwise to promote the + * sale, use or other dealings in this Software without prior written + * authorization. */ #ifndef PARSE_OPTIONS_HPP From 44f70e6b4889dd7891e8cd799e4b602ad65ca152 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Sun, 24 May 2015 18:54:11 -0700 Subject: [PATCH 23/34] Add Coprocess class It provides a convenient way to spawn a process and read from/write to its stdin/stdout. --- Makefile | 4 +- coprocess-unix.cpp | 186 ++++++++++++++++++++++++++++++ coprocess-unix.hpp | 68 +++++++++++ coprocess-win32.cpp | 269 ++++++++++++++++++++++++++++++++++++++++++++ coprocess-win32.hpp | 68 +++++++++++ coprocess.cpp | 5 + coprocess.hpp | 5 + fhstream.cpp | 227 +++++++++++++++++++++++++++++++++++++ fhstream.hpp | 134 ++++++++++++++++++++++ util-unix.cpp | 113 ------------------- util-win32.cpp | 191 ------------------------------- util.cpp | 27 +++++ 12 files changed, 992 insertions(+), 305 deletions(-) create mode 100644 coprocess-unix.cpp create mode 100644 coprocess-unix.hpp create mode 100644 coprocess-win32.cpp create mode 100644 coprocess-win32.hpp create mode 100644 coprocess.cpp create mode 100644 coprocess.hpp create mode 100644 fhstream.cpp create mode 100644 fhstream.hpp diff --git a/Makefile b/Makefile index b53cfb1..57813ce 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,9 @@ OBJFILES = \ gpg.o \ key.o \ util.o \ - parse_options.o + parse_options.o \ + coprocess.o \ + fhstream.o OBJFILES += crypto-openssl.o LDFLAGS += -lcrypto diff --git a/coprocess-unix.cpp b/coprocess-unix.cpp new file mode 100644 index 0000000..f9577e5 --- /dev/null +++ b/coprocess-unix.cpp @@ -0,0 +1,186 @@ +/* + * Copyright 2015 Andrew Ayer + * + * This file is part of git-crypt. + * + * git-crypt is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * git-crypt is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with git-crypt. If not, see . + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify the Program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, the licensors of the Program + * grant you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "coprocess.hpp" +#include "util.hpp" +#include +#include +#include + +static int execvp (const std::string& file, const std::vector& args) +{ + std::vector args_c_str; + args_c_str.reserve(args.size()); + for (std::vector::const_iterator arg(args.begin()); arg != args.end(); ++arg) { + args_c_str.push_back(arg->c_str()); + } + args_c_str.push_back(NULL); + return execvp(file.c_str(), const_cast(&args_c_str[0])); +} + +Coprocess::Coprocess () +{ + pid = -1; + stdin_pipe_reader = -1; + stdin_pipe_writer = -1; + stdin_pipe_ostream = NULL; + stdout_pipe_reader = -1; + stdout_pipe_writer = -1; + stdout_pipe_istream = NULL; +} + +Coprocess::~Coprocess () +{ + close_stdin(); + close_stdout(); +} + +std::ostream* Coprocess::stdin_pipe () +{ + if (!stdin_pipe_ostream) { + int fds[2]; + if (pipe(fds) == -1) { + throw System_error("pipe", "", errno); + } + stdin_pipe_reader = fds[0]; + stdin_pipe_writer = fds[1]; + stdin_pipe_ostream = new ofhstream(this, write_stdin); + } + return stdin_pipe_ostream; +} + +void Coprocess::close_stdin () +{ + delete stdin_pipe_ostream; + stdin_pipe_ostream = NULL; + if (stdin_pipe_writer != -1) { + close(stdin_pipe_writer); + stdin_pipe_writer = -1; + } + if (stdin_pipe_reader != -1) { + close(stdin_pipe_reader); + stdin_pipe_reader = -1; + } +} + +std::istream* Coprocess::stdout_pipe () +{ + if (!stdout_pipe_istream) { + int fds[2]; + if (pipe(fds) == -1) { + throw System_error("pipe", "", errno); + } + stdout_pipe_reader = fds[0]; + stdout_pipe_writer = fds[1]; + stdout_pipe_istream = new ifhstream(this, read_stdout); + } + return stdout_pipe_istream; +} + +void Coprocess::close_stdout () +{ + delete stdout_pipe_istream; + stdout_pipe_istream = NULL; + if (stdout_pipe_writer != -1) { + close(stdout_pipe_writer); + stdout_pipe_writer = -1; + } + if (stdout_pipe_reader != -1) { + close(stdout_pipe_reader); + stdout_pipe_reader = -1; + } +} + +void Coprocess::spawn (const std::vector& args) +{ + pid = fork(); + if (pid == -1) { + throw System_error("fork", "", errno); + } + if (pid == 0) { + if (stdin_pipe_writer != -1) { + close(stdin_pipe_writer); + } + if (stdout_pipe_reader != -1) { + close(stdout_pipe_reader); + } + if (stdin_pipe_reader != -1) { + dup2(stdin_pipe_reader, 0); + close(stdin_pipe_reader); + } + if (stdout_pipe_writer != -1) { + dup2(stdout_pipe_writer, 1); + close(stdout_pipe_writer); + } + + execvp(args[0], args); + perror(args[0].c_str()); + _exit(-1); + } + if (stdin_pipe_reader != -1) { + close(stdin_pipe_reader); + stdin_pipe_reader = -1; + } + if (stdout_pipe_writer != -1) { + close(stdout_pipe_writer); + stdout_pipe_writer = -1; + } +} + +int Coprocess::wait () +{ + int status = 0; + if (waitpid(pid, &status, 0) == -1) { + throw System_error("waitpid", "", errno); + } + return status; +} + +size_t Coprocess::write_stdin (void* handle, const void* buf, size_t count) +{ + const int fd = static_cast(handle)->stdin_pipe_writer; + ssize_t ret; + while ((ret = write(fd, buf, count)) == -1 && errno == EINTR); // restart if interrupted + if (ret < 0) { + throw System_error("write", "", errno); + } + return ret; +} + +size_t Coprocess::read_stdout (void* handle, void* buf, size_t count) +{ + const int fd = static_cast(handle)->stdout_pipe_reader; + ssize_t ret; + while ((ret = read(fd, buf, count)) == -1 && errno == EINTR); // restart if interrupted + if (ret < 0) { + throw System_error("read", "", errno); + } + return ret; +} diff --git a/coprocess-unix.hpp b/coprocess-unix.hpp new file mode 100644 index 0000000..f50f808 --- /dev/null +++ b/coprocess-unix.hpp @@ -0,0 +1,68 @@ +/* + * Copyright 2015 Andrew Ayer + * + * This file is part of git-crypt. + * + * git-crypt is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * git-crypt is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with git-crypt. If not, see . + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify the Program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, the licensors of the Program + * grant you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef GIT_CRYPT_COPROCESS_HPP +#define GIT_CRYPT_COPROCESS_HPP + +#include "fhstream.hpp" +#include +#include + +class Coprocess { + pid_t pid; + + int stdin_pipe_reader; + int stdin_pipe_writer; + ofhstream* stdin_pipe_ostream; + static size_t write_stdin (void*, const void*, size_t); + + int stdout_pipe_reader; + int stdout_pipe_writer; + ifhstream* stdout_pipe_istream; + static size_t read_stdout (void*, void*, size_t); + + Coprocess (const Coprocess&); // Disallow copy + Coprocess& operator= (const Coprocess&); // Disallow assignment +public: + Coprocess (); + ~Coprocess (); + + std::ostream* stdin_pipe (); + void close_stdin (); + + std::istream* stdout_pipe (); + void close_stdout (); + + void spawn (const std::vector&); + + int wait (); +}; + +#endif diff --git a/coprocess-win32.cpp b/coprocess-win32.cpp new file mode 100644 index 0000000..46e21d0 --- /dev/null +++ b/coprocess-win32.cpp @@ -0,0 +1,269 @@ +/* + * Copyright 2015 Andrew Ayer + * + * This file is part of git-crypt. + * + * git-crypt is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * git-crypt is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with git-crypt. If not, see . + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify the Program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, the licensors of the Program + * grant you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "coprocess-win32.hpp" +#include "util.hpp" + + +static void escape_cmdline_argument (std::string& cmdline, const std::string& arg) +{ + // For an explanation of Win32's arcane argument quoting rules, see: + // http://msdn.microsoft.com/en-us/library/17w5ykft%28v=vs.85%29.aspx + // http://msdn.microsoft.com/en-us/library/bb776391%28v=vs.85%29.aspx + // http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx + // http://blogs.msdn.com/b/oldnewthing/archive/2010/09/17/10063629.aspx + cmdline.push_back('"'); + + std::string::const_iterator p(arg.begin()); + while (p != arg.end()) { + if (*p == '"') { + cmdline.push_back('\\'); + cmdline.push_back('"'); + ++p; + } else if (*p == '\\') { + unsigned int num_backslashes = 0; + while (p != arg.end() && *p == '\\') { + ++num_backslashes; + ++p; + } + if (p == arg.end() || *p == '"') { + // Backslashes need to be escaped + num_backslashes *= 2; + } + while (num_backslashes--) { + cmdline.push_back('\\'); + } + } else { + cmdline.push_back(*p++); + } + } + + cmdline.push_back('"'); +} + +static std::string format_cmdline (const std::vector& command) +{ + std::string cmdline; + for (std::vector::const_iterator arg(command.begin()); arg != command.end(); ++arg) { + if (arg != command.begin()) { + cmdline.push_back(' '); + } + escape_cmdline_argument(cmdline, *arg); + } + return cmdline; +} + +static HANDLE spawn_command (const std::vector& command, HANDLE stdin_handle, HANDLE stdout_handle, HANDLE stderr_handle) +{ + PROCESS_INFORMATION proc_info; + ZeroMemory(&proc_info, sizeof(proc_info)); + + STARTUPINFO start_info; + ZeroMemory(&start_info, sizeof(start_info)); + + start_info.cb = sizeof(STARTUPINFO); + start_info.hStdInput = stdin_handle ? stdin_handle : GetStdHandle(STD_INPUT_HANDLE); + start_info.hStdOutput = stdout_handle ? stdout_handle : GetStdHandle(STD_OUTPUT_HANDLE); + start_info.hStdError = stderr_handle ? stderr_handle : GetStdHandle(STD_ERROR_HANDLE); + start_info.dwFlags |= STARTF_USESTDHANDLES; + + std::string cmdline(format_cmdline(command)); + + if (!CreateProcessA(NULL, // application name (NULL to use command line) + const_cast(cmdline.c_str()), + NULL, // process security attributes + NULL, // primary thread security attributes + TRUE, // handles are inherited + 0, // creation flags + NULL, // use parent's environment + NULL, // use parent's current directory + &start_info, + &proc_info)) { + throw System_error("CreateProcess", cmdline, GetLastError()); + } + + CloseHandle(proc_info.hThread); + + return proc_info.hProcess; +} + + +Coprocess::Coprocess () +{ + proc_handle = NULL; + stdin_pipe_reader = NULL; + stdin_pipe_writer = NULL; + stdin_pipe_ostream = NULL; + stdout_pipe_reader = NULL; + stdout_pipe_writer = NULL; + stdout_pipe_istream = NULL; +} + +Coprocess::~Coprocess () +{ + close_stdin(); + close_stdout(); + if (proc_handle) { + CloseHandle(proc_handle); + } +} + +std::ostream* Coprocess::stdin_pipe () +{ + if (!stdin_pipe_ostream) { + SECURITY_ATTRIBUTES sec_attr; + + // Set the bInheritHandle flag so pipe handles are inherited. + sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES); + sec_attr.bInheritHandle = TRUE; + sec_attr.lpSecurityDescriptor = NULL; + + // Create a pipe for the child process's STDIN. + if (!CreatePipe(&stdin_pipe_reader, &stdin_pipe_writer, &sec_attr, 0)) { + throw System_error("CreatePipe", "", GetLastError()); + } + + // Ensure the write handle to the pipe for STDIN is not inherited. + if (!SetHandleInformation(stdin_pipe_writer, HANDLE_FLAG_INHERIT, 0)) { + throw System_error("SetHandleInformation", "", GetLastError()); + } + + stdin_pipe_ostream = new ofhstream(this, write_stdin); + } + return stdin_pipe_ostream; +} + +void Coprocess::close_stdin () +{ + delete stdin_pipe_ostream; + stdin_pipe_ostream = NULL; + if (stdin_pipe_writer) { + CloseHandle(stdin_pipe_writer); + stdin_pipe_writer = NULL; + } + if (stdin_pipe_reader) { + CloseHandle(stdin_pipe_reader); + stdin_pipe_reader = NULL; + } +} + +std::istream* Coprocess::stdout_pipe () +{ + if (!stdout_pipe_istream) { + SECURITY_ATTRIBUTES sec_attr; + + // Set the bInheritHandle flag so pipe handles are inherited. + sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES); + sec_attr.bInheritHandle = TRUE; + sec_attr.lpSecurityDescriptor = NULL; + + // Create a pipe for the child process's STDOUT. + if (!CreatePipe(&stdout_pipe_reader, &stdout_pipe_writer, &sec_attr, 0)) { + throw System_error("CreatePipe", "", GetLastError()); + } + + // Ensure the read handle to the pipe for STDOUT is not inherited. + if (!SetHandleInformation(stdout_pipe_reader, HANDLE_FLAG_INHERIT, 0)) { + throw System_error("SetHandleInformation", "", GetLastError()); + } + + stdout_pipe_istream = new ifhstream(this, read_stdout); + } + return stdout_pipe_istream; +} + +void Coprocess::close_stdout () +{ + delete stdout_pipe_istream; + stdout_pipe_istream = NULL; + if (stdout_pipe_writer) { + CloseHandle(stdout_pipe_writer); + stdout_pipe_writer = NULL; + } + if (stdout_pipe_reader) { + CloseHandle(stdout_pipe_reader); + stdout_pipe_reader = NULL; + } +} + +void Coprocess::spawn (const std::vector& args) +{ + proc_handle = spawn_command(args, stdin_pipe_reader, stdout_pipe_writer, NULL); + if (stdin_pipe_reader) { + CloseHandle(stdin_pipe_reader); + stdin_pipe_reader = NULL; + } + if (stdout_pipe_writer) { + CloseHandle(stdout_pipe_writer); + stdout_pipe_writer = NULL; + } +} + +int Coprocess::wait () +{ + if (WaitForSingleObject(proc_handle, INFINITE) == WAIT_FAILED) { + throw System_error("WaitForSingleObject", "", GetLastError()); + } + + DWORD exit_code; + if (!GetExitCodeProcess(proc_handle, &exit_code)) { + throw System_error("GetExitCodeProcess", "", GetLastError()); + } + + return exit_code; +} + +size_t Coprocess::write_stdin (void* handle, const void* buf, size_t count) +{ + DWORD bytes_written; + if (!WriteFile(static_cast(handle)->stdin_pipe_writer, buf, count, &bytes_written, NULL)) { + throw System_error("WriteFile", "", GetLastError()); + } + return bytes_written; +} + +size_t Coprocess::read_stdout (void* handle, void* buf, size_t count) +{ + // Note that ReadFile on a pipe may return with bytes_read==0 if the other + // end of the pipe writes zero bytes, so retry when this happens. + // When the other end of the pipe actually closes, ReadFile + // fails with ERROR_BROKEN_PIPE. + DWORD bytes_read; + do { + if (!ReadFile(static_cast(handle)->stdout_pipe_reader, buf, count, &bytes_read, NULL)) { + const DWORD read_error = GetLastError(); + if (read_error != ERROR_BROKEN_PIPE) { + throw System_error("ReadFile", "", read_error); + } + return 0; + } + } while (bytes_read == 0); + return bytes_read; +} diff --git a/coprocess-win32.hpp b/coprocess-win32.hpp new file mode 100644 index 0000000..532728d --- /dev/null +++ b/coprocess-win32.hpp @@ -0,0 +1,68 @@ +/* + * Copyright 2015 Andrew Ayer + * + * This file is part of git-crypt. + * + * git-crypt is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * git-crypt is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with git-crypt. If not, see . + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify the Program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, the licensors of the Program + * grant you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef GIT_CRYPT_COPROCESS_HPP +#define GIT_CRYPT_COPROCESS_HPP + +#include "fhstream.hpp" +#include +#include + +class Coprocess { + HANDLE proc_handle; + + HANDLE stdin_pipe_reader; + HANDLE stdin_pipe_writer; + ofhstream* stdin_pipe_ostream; + static size_t write_stdin (void*, const void*, size_t); + + HANDLE stdout_pipe_reader; + HANDLE stdout_pipe_writer; + ifhstream* stdout_pipe_istream; + static size_t read_stdout (void*, void*, size_t); + + Coprocess (const Coprocess&); // Disallow copy + Coprocess& operator= (const Coprocess&); // Disallow assignment +public: + Coprocess (); + ~Coprocess (); + + std::ostream* stdin_pipe (); + void close_stdin (); + + std::istream* stdout_pipe (); + void close_stdout (); + + void spawn (const std::vector&); + + int wait (); +}; + +#endif diff --git a/coprocess.cpp b/coprocess.cpp new file mode 100644 index 0000000..813cc2a --- /dev/null +++ b/coprocess.cpp @@ -0,0 +1,5 @@ +#ifdef _WIN32 +#include "coprocess-win32.cpp" +#else +#include "coprocess-unix.cpp" +#endif diff --git a/coprocess.hpp b/coprocess.hpp new file mode 100644 index 0000000..f8ac370 --- /dev/null +++ b/coprocess.hpp @@ -0,0 +1,5 @@ +#ifdef _WIN32 +#include "coprocess-win32.hpp" +#else +#include "coprocess-unix.hpp" +#endif diff --git a/fhstream.cpp b/fhstream.cpp new file mode 100644 index 0000000..8c8fc79 --- /dev/null +++ b/fhstream.cpp @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2012, 2015 Andrew Ayer + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Except as contained in this notice, the name(s) of the above copyright + * holders shall not be used in advertising or otherwise to promote the + * sale, use or other dealings in this Software without prior written + * authorization. + */ + +#include +#include // for std::min + +#include "fhstream.hpp" + +/* + * ofhstream + */ + +ofhbuf::ofhbuf (void* arg_handle, size_t (*arg_write_fun)(void*, const void*, size_t)) +: handle(arg_handle), + write_fun(arg_write_fun), + buffer(new char[default_buffer_size]), + buffer_size(default_buffer_size) +{ + reset_buffer(); +} + +ofhbuf::~ofhbuf () +{ + if (handle) { + try { + sync(); + } catch (...) { + // Ignore exception since we're in the destructor. + // To catch write errors, call sync() explicitly. + } + } + delete[] buffer; +} + +ofhbuf::int_type ofhbuf::overflow (ofhbuf::int_type c) +{ + const char* p = pbase(); + std::streamsize bytes_to_write = pptr() - p; + + if (!is_eof(c)) { + *pptr() = c; + ++bytes_to_write; + } + + while (bytes_to_write > 0) { + const size_t bytes_written = write_fun(handle, p, bytes_to_write); + bytes_to_write -= bytes_written; + p += bytes_written; + } + + reset_buffer(); + + return traits_type::to_int_type(0); +} + +int ofhbuf::sync () +{ + return !is_eof(overflow(traits_type::eof())) ? 0 : -1; +} + +std::streamsize ofhbuf::xsputn (const char* s, std::streamsize n) +{ + // Use heuristic to decide whether to write directly or just use buffer + // Write directly only if n >= MIN(4096, available buffer capacity) + // (this is similar to what basic_filebuf does) + + if (n < std::min(4096, epptr() - pptr())) { + // Not worth it to do a direct write + return std::streambuf::xsputn(s, n); + } + + // Before we can do a direct write of this string, we need to flush + // out the current contents of the buffer. + if (pbase() != pptr()) { + overflow(traits_type::eof()); // throws an exception or it succeeds + } + + // Now we can go ahead and write out the string. + size_t bytes_to_write = n; + + while (bytes_to_write > 0) { + const size_t bytes_written = write_fun(handle, s, bytes_to_write); + bytes_to_write -= bytes_written; + s += bytes_written; + } + + return n; // Return the total bytes written +} + +std::streambuf* ofhbuf::setbuf (char* s, std::streamsize n) +{ + if (s == 0 && n == 0) { + // Switch to unbuffered + // This won't take effect until the next overflow or sync + // (We defer it taking effect so that write errors can be properly reported) + // To cause it to take effect as soon as possible, we artificially reduce the + // size of the buffer so it has no space left. This will trigger an overflow + // on the next put. + std::streambuf::setp(pbase(), pptr()); + std::streambuf::pbump(pptr() - pbase()); + buffer_size = 1; + } + return this; +} + + + +/* + * ifhstream + */ + +ifhbuf::ifhbuf (void* arg_handle, size_t (*arg_read_fun)(void*, void*, size_t)) +: handle(arg_handle), + read_fun(arg_read_fun), + buffer(new char[default_buffer_size + putback_size]), + buffer_size(default_buffer_size) +{ + reset_buffer(0, 0); +} + +ifhbuf::~ifhbuf () +{ + delete[] buffer; +} + +ifhbuf::int_type ifhbuf::underflow () +{ + if (gptr() >= egptr()) { // A true underflow (no bytes in buffer left to read) + + // Move the putback_size most-recently-read characters into the putback area + size_t nputback = std::min(gptr() - eback(), putback_size); + std::memmove(buffer + (putback_size - nputback), gptr() - nputback, nputback); + + // Now read new characters from the file descriptor + const size_t nread = read_fun(handle, buffer + putback_size, buffer_size); + if (nread == 0) { + // EOF + return traits_type::eof(); + } + + // Reset the buffer + reset_buffer(nputback, nread); + } + + // Return the next character + return traits_type::to_int_type(*gptr()); +} + +std::streamsize ifhbuf::xsgetn (char* s, std::streamsize n) +{ + // Use heuristic to decide whether to read directly + // Read directly only if n >= bytes_available + 4096 + + std::streamsize bytes_available = egptr() - gptr(); + + if (n < bytes_available + 4096) { + // Not worth it to do a direct read + return std::streambuf::xsgetn(s, n); + } + + std::streamsize total_bytes_read = 0; + + // First, copy out the bytes currently in the buffer + std::memcpy(s, gptr(), bytes_available); + + s += bytes_available; + n -= bytes_available; + total_bytes_read += bytes_available; + + // Now do the direct read + while (n > 0) { + const size_t bytes_read = read_fun(handle, s, n); + if (bytes_read == 0) { + // EOF + break; + } + + s += bytes_read; + n -= bytes_read; + total_bytes_read += bytes_read; + } + + // Fill up the putback area with the most recently read characters + size_t nputback = std::min(total_bytes_read, putback_size); + std::memcpy(buffer + (putback_size - nputback), s - nputback, nputback); + + // Reset the buffer with no bytes available for reading, but with some putback characters + reset_buffer(nputback, 0); + + // Return the total number of bytes read + return total_bytes_read; +} + +std::streambuf* ifhbuf::setbuf (char* s, std::streamsize n) +{ + if (s == 0 && n == 0) { + // Switch to unbuffered + // This won't take effect until the next underflow (we don't want to + // lose what's currently in the buffer!) + buffer_size = 1; + } + return this; +} diff --git a/fhstream.hpp b/fhstream.hpp new file mode 100644 index 0000000..28bb1c7 --- /dev/null +++ b/fhstream.hpp @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2012, 2015 Andrew Ayer + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Except as contained in this notice, the name(s) of the above copyright + * holders shall not be used in advertising or otherwise to promote the + * sale, use or other dealings in this Software without prior written + * authorization. + */ + +#ifndef GIT_CRYPT_FHSTREAM_HPP +#define GIT_CRYPT_FHSTREAM_HPP + +#include +#include +#include + +/* + * ofhstream + */ +class ofhbuf : public std::streambuf { + enum { default_buffer_size = 8192 }; + + void* handle; + size_t (*write_fun)(void*, const void*, size_t); + char* buffer; + size_t buffer_size; + + inline void reset_buffer () + { + std::streambuf::setp(buffer, buffer + buffer_size - 1); + } + static inline bool is_eof (int_type ch) { return traits_type::eq_int_type(ch, traits_type::eof()); } + + // Disallow copy +#if __cplusplus >= 201103L /* C++11 */ + ofhbuf (const ofhbuf&) = delete; + ofhbuf& operator= (const ofhbuf&) = delete; +#else + ofhbuf (const ofhbuf&); + ofhbuf& operator= (const ofhbuf&); +#endif + +protected: + virtual int_type overflow (int_type ch =traits_type::eof()); + virtual int sync (); + virtual std::streamsize xsputn (const char*, std::streamsize); + virtual std::streambuf* setbuf (char*, std::streamsize); + +public: + ofhbuf (void*, size_t (*)(void*, const void*, size_t)); + ~ofhbuf (); // WARNING: calls sync() and ignores exceptions +}; + +class ofhstream : public std::ostream { + mutable ofhbuf buf; +public: + ofhstream (void* handle, size_t (*write_fun)(void*, const void*, size_t)) + : std::ostream(0), buf(handle, write_fun) + { + std::ostream::rdbuf(&buf); + } + + ofhbuf* rdbuf () const { return &buf; } +}; + + +/* + * ifhstream + */ +class ifhbuf : public std::streambuf { + enum { + default_buffer_size = 8192, + putback_size = 4 + }; + + void* handle; + size_t (*read_fun)(void*, void*, size_t); + char* buffer; + size_t buffer_size; + + inline void reset_buffer (size_t nputback, size_t nread) + { + std::streambuf::setg(buffer + (putback_size - nputback), buffer + putback_size, buffer + putback_size + nread); + } + // Disallow copy +#if __cplusplus >= 201103L /* C++11 */ + ifhbuf (const ifhbuf&) = delete; + ifhbuf& operator= (const ifhbuf&) = delete; +#else + ifhbuf (const ifhbuf&); + ifhbuf& operator= (const ifhbuf&); +#endif + +protected: + virtual int_type underflow (); + virtual std::streamsize xsgetn (char*, std::streamsize); + virtual std::streambuf* setbuf (char*, std::streamsize); + +public: + ifhbuf (void*, size_t (*)(void*, void*, size_t)); + ~ifhbuf (); // Can't fail +}; + +class ifhstream : public std::istream { + mutable ifhbuf buf; +public: + explicit ifhstream (void* handle, size_t (*read_fun)(void*, void*, size_t)) + : std::istream(0), buf(handle, read_fun) + { + std::istream::rdbuf(&buf); + } + + ifhbuf* rdbuf () const { return &buf; } +}; + +#endif diff --git a/util-unix.cpp b/util-unix.cpp index 4eed56d..7c7c05b 100644 --- a/util-unix.cpp +++ b/util-unix.cpp @@ -162,119 +162,6 @@ std::string our_exe_path () } } -static int execvp (const std::string& file, const std::vector& args) -{ - std::vector args_c_str; - args_c_str.reserve(args.size()); - for (std::vector::const_iterator arg(args.begin()); arg != args.end(); ++arg) { - args_c_str.push_back(arg->c_str()); - } - args_c_str.push_back(NULL); - return execvp(file.c_str(), const_cast(&args_c_str[0])); -} - -int exec_command (const std::vector& command) -{ - pid_t child = fork(); - if (child == -1) { - throw System_error("fork", "", errno); - } - if (child == 0) { - execvp(command[0], command); - perror(command[0].c_str()); - _exit(-1); - } - int status = 0; - if (waitpid(child, &status, 0) == -1) { - throw System_error("waitpid", "", errno); - } - return status; -} - -int exec_command (const std::vector& command, std::ostream& output) -{ - int pipefd[2]; - if (pipe(pipefd) == -1) { - throw System_error("pipe", "", errno); - } - pid_t child = fork(); - if (child == -1) { - int fork_errno = errno; - close(pipefd[0]); - close(pipefd[1]); - throw System_error("fork", "", fork_errno); - } - if (child == 0) { - close(pipefd[0]); - if (pipefd[1] != 1) { - dup2(pipefd[1], 1); - close(pipefd[1]); - } - execvp(command[0], command); - perror(command[0].c_str()); - _exit(-1); - } - close(pipefd[1]); - char buffer[1024]; - ssize_t bytes_read; - while ((bytes_read = read(pipefd[0], buffer, sizeof(buffer))) > 0) { - output.write(buffer, bytes_read); - } - if (bytes_read == -1) { - int read_errno = errno; - close(pipefd[0]); - throw System_error("read", "", read_errno); - } - close(pipefd[0]); - int status = 0; - if (waitpid(child, &status, 0) == -1) { - throw System_error("waitpid", "", errno); - } - return status; -} - -int exec_command_with_input (const std::vector& command, const char* p, size_t len) -{ - int pipefd[2]; - if (pipe(pipefd) == -1) { - throw System_error("pipe", "", errno); - } - pid_t child = fork(); - if (child == -1) { - int fork_errno = errno; - close(pipefd[0]); - close(pipefd[1]); - throw System_error("fork", "", fork_errno); - } - if (child == 0) { - close(pipefd[1]); - if (pipefd[0] != 0) { - dup2(pipefd[0], 0); - close(pipefd[0]); - } - execvp(command[0], command); - perror(command[0].c_str()); - _exit(-1); - } - close(pipefd[0]); - while (len > 0) { - ssize_t bytes_written = write(pipefd[1], p, len); - if (bytes_written == -1) { - int write_errno = errno; - close(pipefd[1]); - throw System_error("write", "", write_errno); - } - p += bytes_written; - len -= bytes_written; - } - close(pipefd[1]); - int status = 0; - if (waitpid(child, &status, 0) == -1) { - throw System_error("waitpid", "", errno); - } - return status; -} - int exit_status (int wait_status) { return wait_status != -1 && WIFEXITED(wait_status) ? WEXITSTATUS(wait_status) : -1; diff --git a/util-win32.cpp b/util-win32.cpp index ceda202..445d185 100644 --- a/util-win32.cpp +++ b/util-win32.cpp @@ -125,197 +125,6 @@ std::string our_exe_path () return std::string(buffer.begin(), buffer.begin() + len); } -static void escape_cmdline_argument (std::string& cmdline, const std::string& arg) -{ - // For an explanation of Win32's arcane argument quoting rules, see: - // http://msdn.microsoft.com/en-us/library/17w5ykft%28v=vs.85%29.aspx - // http://msdn.microsoft.com/en-us/library/bb776391%28v=vs.85%29.aspx - // http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx - // http://blogs.msdn.com/b/oldnewthing/archive/2010/09/17/10063629.aspx - cmdline.push_back('"'); - - std::string::const_iterator p(arg.begin()); - while (p != arg.end()) { - if (*p == '"') { - cmdline.push_back('\\'); - cmdline.push_back('"'); - ++p; - } else if (*p == '\\') { - unsigned int num_backslashes = 0; - while (p != arg.end() && *p == '\\') { - ++num_backslashes; - ++p; - } - if (p == arg.end() || *p == '"') { - // Backslashes need to be escaped - num_backslashes *= 2; - } - while (num_backslashes--) { - cmdline.push_back('\\'); - } - } else { - cmdline.push_back(*p++); - } - } - - cmdline.push_back('"'); -} - -static std::string format_cmdline (const std::vector& command) -{ - std::string cmdline; - for (std::vector::const_iterator arg(command.begin()); arg != command.end(); ++arg) { - if (arg != command.begin()) { - cmdline.push_back(' '); - } - escape_cmdline_argument(cmdline, *arg); - } - return cmdline; -} - -static int wait_for_child (HANDLE child_handle) -{ - if (WaitForSingleObject(child_handle, INFINITE) == WAIT_FAILED) { - throw System_error("WaitForSingleObject", "", GetLastError()); - } - - DWORD exit_code; - if (!GetExitCodeProcess(child_handle, &exit_code)) { - throw System_error("GetExitCodeProcess", "", GetLastError()); - } - - return exit_code; -} - -static HANDLE spawn_command (const std::vector& command, HANDLE stdin_handle, HANDLE stdout_handle, HANDLE stderr_handle) -{ - PROCESS_INFORMATION proc_info; - ZeroMemory(&proc_info, sizeof(proc_info)); - - STARTUPINFO start_info; - ZeroMemory(&start_info, sizeof(start_info)); - - start_info.cb = sizeof(STARTUPINFO); - start_info.hStdInput = stdin_handle ? stdin_handle : GetStdHandle(STD_INPUT_HANDLE); - start_info.hStdOutput = stdout_handle ? stdout_handle : GetStdHandle(STD_OUTPUT_HANDLE); - start_info.hStdError = stderr_handle ? stderr_handle : GetStdHandle(STD_ERROR_HANDLE); - start_info.dwFlags |= STARTF_USESTDHANDLES; - - std::string cmdline(format_cmdline(command)); - - if (!CreateProcessA(NULL, // application name (NULL to use command line) - const_cast(cmdline.c_str()), - NULL, // process security attributes - NULL, // primary thread security attributes - TRUE, // handles are inherited - 0, // creation flags - NULL, // use parent's environment - NULL, // use parent's current directory - &start_info, - &proc_info)) { - throw System_error("CreateProcess", cmdline, GetLastError()); - } - - CloseHandle(proc_info.hThread); - - return proc_info.hProcess; -} - -int exec_command (const std::vector& command) -{ - HANDLE child_handle = spawn_command(command, NULL, NULL, NULL); - int exit_code = wait_for_child(child_handle); - CloseHandle(child_handle); - return exit_code; -} - -int exec_command (const std::vector& command, std::ostream& output) -{ - HANDLE stdout_pipe_reader = NULL; - HANDLE stdout_pipe_writer = NULL; - SECURITY_ATTRIBUTES sec_attr; - - // Set the bInheritHandle flag so pipe handles are inherited. - sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES); - sec_attr.bInheritHandle = TRUE; - sec_attr.lpSecurityDescriptor = NULL; - - // Create a pipe for the child process's STDOUT. - if (!CreatePipe(&stdout_pipe_reader, &stdout_pipe_writer, &sec_attr, 0)) { - throw System_error("CreatePipe", "", GetLastError()); - } - - // Ensure the read handle to the pipe for STDOUT is not inherited. - if (!SetHandleInformation(stdout_pipe_reader, HANDLE_FLAG_INHERIT, 0)) { - throw System_error("SetHandleInformation", "", GetLastError()); - } - - HANDLE child_handle = spawn_command(command, NULL, stdout_pipe_writer, NULL); - CloseHandle(stdout_pipe_writer); - - // Read from stdout_pipe_reader. - // Note that ReadFile on a pipe may return with bytes_read==0 if the other - // end of the pipe writes zero bytes, so don't break out of the read loop - // when this happens. When the other end of the pipe closes, ReadFile - // fails with ERROR_BROKEN_PIPE. - char buffer[1024]; - DWORD bytes_read; - while (ReadFile(stdout_pipe_reader, buffer, sizeof(buffer), &bytes_read, NULL)) { - output.write(buffer, bytes_read); - } - const DWORD read_error = GetLastError(); - if (read_error != ERROR_BROKEN_PIPE) { - throw System_error("ReadFile", "", read_error); - } - - CloseHandle(stdout_pipe_reader); - - int exit_code = wait_for_child(child_handle); - CloseHandle(child_handle); - return exit_code; -} - -int exec_command_with_input (const std::vector& command, const char* p, size_t len) -{ - HANDLE stdin_pipe_reader = NULL; - HANDLE stdin_pipe_writer = NULL; - SECURITY_ATTRIBUTES sec_attr; - - // Set the bInheritHandle flag so pipe handles are inherited. - sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES); - sec_attr.bInheritHandle = TRUE; - sec_attr.lpSecurityDescriptor = NULL; - - // Create a pipe for the child process's STDIN. - if (!CreatePipe(&stdin_pipe_reader, &stdin_pipe_writer, &sec_attr, 0)) { - throw System_error("CreatePipe", "", GetLastError()); - } - - // Ensure the write handle to the pipe for STDIN is not inherited. - if (!SetHandleInformation(stdin_pipe_writer, HANDLE_FLAG_INHERIT, 0)) { - throw System_error("SetHandleInformation", "", GetLastError()); - } - - HANDLE child_handle = spawn_command(command, stdin_pipe_reader, NULL, NULL); - CloseHandle(stdin_pipe_reader); - - // Write to stdin_pipe_writer. - while (len > 0) { - DWORD bytes_written; - if (!WriteFile(stdin_pipe_writer, p, len, &bytes_written, NULL)) { - throw System_error("WriteFile", "", GetLastError()); - } - p += bytes_written; - len -= bytes_written; - } - - CloseHandle(stdin_pipe_writer); - - int exit_code = wait_for_child(child_handle); - CloseHandle(child_handle); - return exit_code; -} - int exit_status (int status) { return status; diff --git a/util.cpp b/util.cpp index 2da0622..4e1aa78 100644 --- a/util.cpp +++ b/util.cpp @@ -30,9 +30,36 @@ #include "git-crypt.hpp" #include "util.hpp" +#include "coprocess.hpp" #include #include +int exec_command (const std::vector& args) +{ + Coprocess proc; + proc.spawn(args); + return proc.wait(); +} + +int exec_command (const std::vector& args, std::ostream& output) +{ + Coprocess proc; + std::istream* proc_stdout = proc.stdout_pipe(); + proc.spawn(args); + output << proc_stdout->rdbuf(); + return proc.wait(); +} + +int exec_command_with_input (const std::vector& args, const char* p, size_t len) +{ + Coprocess proc; + std::ostream* proc_stdin = proc.stdin_pipe(); + proc.spawn(args); + proc_stdin->write(p, len); + proc.close_stdin(); + return proc.wait(); +} + std::string escape_shell_arg (const std::string& str) { std::string new_str; From 1988ee3819677a026d2bfb5a734b47a7c5fe8316 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Sun, 24 May 2015 18:55:56 -0700 Subject: [PATCH 24/34] Speed up lock/unlock by using single git check-attr process Previously, lock/unlock needed to spawn a separate `git check-attr` process for every single file in the repository (whether encrypted or not). This was extremely inefficient, especially on Windows. Now, git-crypt spawns a single `git check-attr` process and communicates with it over stdin. In a repository with thousands of files, this results in a speedup of nearly 100x. This relies on the --stdin and -z options to `git check-attr`, which were only added in Git 1.8.5 (released 27 Nov 2013). With older versions of Git, git-crypt falls back to the old and slower code. --- commands.cpp | 102 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 86 insertions(+), 16 deletions(-) diff --git a/commands.cpp b/commands.cpp index f8e9f23..19111e3 100644 --- a/commands.cpp +++ b/commands.cpp @@ -34,6 +34,7 @@ #include "key.hpp" #include "gpg.hpp" #include "parse_options.hpp" +#include "coprocess.hpp" #include #include #include @@ -326,7 +327,6 @@ static void get_git_status (std::ostream& output) static std::pair get_file_attributes (const std::string& filename) { // git check-attr filter diff -- filename - // TODO: pass -z to get machine-parseable output (this requires Git 1.8.5 or higher, which was released on 27 Nov 2013) std::vector command; command.push_back("git"); command.push_back("check-attr"); @@ -375,6 +375,36 @@ static std::pair get_file_attributes (const std::strin return std::make_pair(filter_attr, diff_attr); } +// returns filter and diff attributes as a pair +static std::pair get_file_attributes (const std::string& filename, std::ostream& check_attr_stdin, std::istream& check_attr_stdout) +{ + check_attr_stdin << filename << '\0' << std::flush; + + std::string filter_attr; + std::string diff_attr; + + // Example output: + // filename\0filter\0git-crypt\0filename\0diff\0git-crypt\0 + for (int i = 0; i < 2; ++i) { + std::string filename; + std::string attr_name; + std::string attr_value; + std::getline(check_attr_stdout, filename, '\0'); + std::getline(check_attr_stdout, attr_name, '\0'); + std::getline(check_attr_stdout, attr_value, '\0'); + + if (attr_value != "unspecified" && attr_value != "unset" && attr_value != "set") { + if (attr_name == "filter") { + filter_attr = attr_value; + } else if (attr_name == "diff") { + diff_attr = attr_value; + } + } + } + + return std::make_pair(filter_attr, diff_attr); +} + static bool check_if_blob_is_encrypted (const std::string& object_id) { // git cat-file blob object_id @@ -430,32 +460,72 @@ static bool is_git_file_mode (const std::string& mode) static void get_encrypted_files (std::vector& files, const char* key_name) { // git ls-files -cz -- path_to_top - std::vector command; - command.push_back("git"); - command.push_back("ls-files"); - command.push_back("-csz"); - command.push_back("--"); + std::vector ls_files_command; + ls_files_command.push_back("git"); + ls_files_command.push_back("ls-files"); + ls_files_command.push_back("-csz"); + ls_files_command.push_back("--"); const std::string path_to_top(get_path_to_top()); if (!path_to_top.empty()) { - command.push_back(path_to_top); + ls_files_command.push_back(path_to_top); } - std::stringstream output; - if (!successful_exit(exec_command(command, output))) { - throw Error("'git ls-files' failed - is this a Git repository?"); + Coprocess ls_files; + std::istream* ls_files_stdout = ls_files.stdout_pipe(); + ls_files.spawn(ls_files_command); + + Coprocess check_attr; + std::ostream* check_attr_stdin = NULL; + std::istream* check_attr_stdout = NULL; + if (git_version() >= make_version(1, 8, 5)) { + // In Git 1.8.5 (released 27 Nov 2013) and higher, we use a single `git check-attr` process + // to get the attributes of all files at once. In prior versions, we have to fork and exec + // a separate `git check-attr` process for each file, since -z and --stdin aren't supported. + // In a repository with thousands of files, this results in an almost 100x speedup. + std::vector check_attr_command; + check_attr_command.push_back("git"); + check_attr_command.push_back("check-attr"); + check_attr_command.push_back("--stdin"); + check_attr_command.push_back("-z"); + check_attr_command.push_back("filter"); + check_attr_command.push_back("diff"); + + check_attr_stdin = check_attr.stdin_pipe(); + check_attr_stdout = check_attr.stdout_pipe(); + check_attr.spawn(check_attr_command); } - while (output.peek() != -1) { + while (ls_files_stdout->peek() != -1) { std::string mode; std::string object_id; std::string stage; std::string filename; - output >> mode >> object_id >> stage >> std::ws; - std::getline(output, filename, '\0'); + *ls_files_stdout >> mode >> object_id >> stage >> std::ws; + std::getline(*ls_files_stdout, filename, '\0'); - // TODO: get file attributes en masse for efficiency... unfortunately this requires machine-parseable output from git check-attr to be workable, and this is only supported in Git 1.8.5 and above (released 27 Nov 2013) - if (is_git_file_mode(mode) && get_file_attributes(filename).first == attribute_name(key_name)) { - files.push_back(filename); + if (is_git_file_mode(mode)) { + std::string filter_attribute; + + if (check_attr_stdin) { + filter_attribute = get_file_attributes(filename, *check_attr_stdin, *check_attr_stdout).first; + } else { + filter_attribute = get_file_attributes(filename).first; + } + + if (filter_attribute == attribute_name(key_name)) { + files.push_back(filename); + } + } + } + + if (!successful_exit(ls_files.wait())) { + throw Error("'git ls-files' failed - is this a Git repository?"); + } + + if (check_attr_stdin) { + check_attr.close_stdin(); + if (!successful_exit(check_attr.wait())) { + throw Error("'git check-attr' failed - is this a Git repository?"); } } } From 4acb4205ebbf64b6f2faf198a71a6134b3be7416 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Sun, 24 May 2015 18:58:13 -0700 Subject: [PATCH 25/34] git_version: cache the Git version (to avoid repeated invocations of `git version`) --- commands.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/commands.cpp b/commands.cpp index 19111e3..2b86930 100644 --- a/commands.cpp +++ b/commands.cpp @@ -89,9 +89,10 @@ static std::vector parse_version (const std::string& str) return version; } -static std::vector git_version () +static const std::vector& git_version () { - return parse_version(git_version_string()); + static const std::vector version(parse_version(git_version_string())); + return version; } static std::vector make_version (int a, int b, int c) From ca98d1aed151816978b178738d477771aa40d0f2 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Mon, 25 May 2015 15:25:44 -0700 Subject: [PATCH 26/34] Minor README tweaks --- README | 8 +++++++- README.md | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/README b/README index 5e24bc7..5d2477e 100644 --- a/README +++ b/README @@ -34,7 +34,9 @@ Specify files to encrypt by creating a .gitattributes file: Like a .gitignore file, it can match wildcards and should be checked into the repository. See below for more information about .gitattributes. Make sure you don't accidentally encrypt the .gitattributes file itself -(or other git files like .gitignore or .gitmodules). +(or other git files like .gitignore or .gitmodules). Make sure your +.gitattributes rules are in place *before* you add sensitive files, or +those files won't be encrypted! Share the repository with others (or with yourself) using GPG: @@ -102,6 +104,10 @@ instead. (Note: no endorsement is made of git-remote-gcrypt's security.) git-crypt does not encrypt file names, commit messages, symlink targets, gitlinks, or other metadata. +git-crypt does not hide when a file does or doesn't change, the length +of a file, or the fact that two files are identical (see "Security" +section above). + Files encrypted with git-crypt are not compressible. Even the smallest change to an encrypted file requires git to store the entire changed file, instead of just a delta. diff --git a/README.md b/README.md index 5958a7c..2fffbef 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,9 @@ Specify files to encrypt by creating a .gitattributes file: Like a .gitignore file, it can match wildcards and should be checked into the repository. See below for more information about .gitattributes. Make sure you don't accidentally encrypt the .gitattributes file itself -(or other git files like .gitignore or .gitmodules). +(or other git files like .gitignore or .gitmodules). Make sure your +.gitattributes rules are in place *before* you add sensitive files, or +those files won't be encrypted! Share the repository with others (or with yourself) using GPG: @@ -104,6 +106,10 @@ instead. (Note: no endorsement is made of git-remote-gcrypt's security.) git-crypt does not encrypt file names, commit messages, symlink targets, gitlinks, or other metadata. +git-crypt does not hide when a file does or doesn't change, the length +of a file, or the fact that two files are identical (see "Security" +section above). + Files encrypted with git-crypt are not compressible. Even the smallest change to an encrypted file requires git to store the entire changed file, instead of just a delta. From 0a2e633d7f06ed99a203426a5fa55f3da3a4a0b2 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Mon, 25 May 2015 15:26:32 -0700 Subject: [PATCH 27/34] Recommend Git 1.8.5 or newer for best performance Since older versions of Git have to spawn a separate check-attr process for every file in the repository, which is slow. --- INSTALL | 2 ++ INSTALL.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/INSTALL b/INSTALL index 5df9b09..39af571 100644 --- a/INSTALL +++ b/INSTALL @@ -16,6 +16,8 @@ To use git-crypt, you need: Git 1.7.2 or newer git git OpenSSL openssl openssl +Note: Git 1.8.5 or newer is recommended for best performance. + BUILDING GIT-CRYPT diff --git a/INSTALL.md b/INSTALL.md index b129565..5c8f592 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -16,6 +16,8 @@ To use git-crypt, you need: Git 1.7.2 or newer | git | git OpenSSL | openssl | openssl +Note: Git 1.8.5 or newer is recommended for best performance. + ### Building git-crypt From b4569ae6c1e9de6f30c0b2f775fb379c8f309a82 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Thu, 28 May 2015 21:24:16 -0700 Subject: [PATCH 28/34] Add man page --- man/git-crypt.xml | 493 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 493 insertions(+) create mode 100644 man/git-crypt.xml diff --git a/man/git-crypt.xml b/man/git-crypt.xml new file mode 100644 index 0000000..a444368 --- /dev/null +++ b/man/git-crypt.xml @@ -0,0 +1,493 @@ + + + + + git-crypt + 2015-05-17 + git-crypt 0.4.3 + + + Andrew Ayer + + agwa@andrewayer.name + https://www.agwa.name + + + + + git-crypt + 1 + + + + git-crypt + transparent file encryption in Git + + + + + git-crypt OPTIONS COMMAND ARGS + + + + + Common commands + + git-crypt init + + + git-crypt status + + + git-crypt lock + + + + GPG commands + + git-crypt add-gpg-user GPG_USER_ID + + + git-crypt unlock + + + + Symmetric key commands + + git-crypt export-key OUTPUT_KEY_FILE + + + git-crypt unlock KEY_FILE + + + + + Description + + + git-crypt enables transparent encryption and decryption + of files in a git repository. Files which you choose to protect are encrypted when committed, + and decrypted when checked out. git-crypt lets you freely share a repository containing a mix of + public and private content. git-crypt gracefully degrades, so developers without the secret key + can still clone and commit to a repository with encrypted files. This lets you store your secret + material (such as keys or passwords) in the same repository as your code, without requiring you + to lock down your entire repository. + + + + + Commands + + + git-crypt is logically divided into several sub-commands which + perform distinct tasks. Each sub-command, and its arguments, + are documented below. Note that arguments and options to sub-commands must be + specified on the command line after the name of the sub-command. + + + + + + + + Generate a key and prepare the current Git repository to use git-crypt. + + + + The following options are understood: + + + + KEY_NAME + KEY_NAME + + + + Initialize the given key instead of the default key. git-crypt + supports multiple keys per repository, allowing you to share + different files with different sets of collaborators. + + + + + + + + + + + + Display a list of files in the repository, with their status (encrypted or unencrypted). + + + + The following options are understood: + + + + + + + + Show only encrypted files. + + + + + + + + + Show only unencrypted files. + + + + + + + + + + Encrypt files that should be encrypted but were + committed to the repository or added to the index + without encryption. (This can happen if a file + is added before git-crypt is initialized or before + the file is added to the gitattributes file.) + + + + + + + + + + + + Add the users with the given GPG user IDs as collaborators. Specifically, + git-crypt uses gpg1 + to encrypt the shared symmetric key + to the public keys of each GPG user ID, and stores the GPG-encrypted + keys in the .git-crypt directory at the root of the repository. + + + + GPG_USER_ID can be a key ID, a full fingerprint, an email address, or anything + else that uniquely identifies a public key to GPG (see "HOW TO SPECIFY + A USER ID" in the gpg1 + man page). + + + + The following options are understood: + + + + KEY_NAME + KEY_NAME + + + + Grant access to the given key, rather than the default key. + + + + + + + + + + Don't automatically commit the changes to the .git-crypt + directory. + + + + + + + + + Assume that the GPG keys specified on the command line are trusted; + i.e. they actually belong to the users that they claim to belong to. + + + Without this option, git-crypt uses the same trust model as GPG, + which is based on the Web of Trust by default. Under this + model, git-crypt will reject GPG keys that do not have + trusted signatures. + + + If you don't want to use the Web of Trust, you can either change + GPG's trust model by setting the + option in ~/.gnupg/gpg.conf (see + gpg1), + or use the option to add-gpg-user + on a case-by-case basis. + + + + + + + + + + + + Decrypt the repository. If one or more key files are specified on the command line, + git-crypt attempts to decrypt using those shared symmetric keys. If no key files + are specified, git-crypt attempts to decrypt using a GPG-encrypted key stored in + the repository's .git-crypt directory. + + + + This command takes no options. + + + + + + + + + Export the repository's shared symmetric key to the given file. + + + + The following options are understood: + + + + KEY_NAME + KEY_NAME + + + + Export the given key, rather than the default key. + + + + + + + + + + + + Display help for the given COMMAND, + or an overview of all commands if no command is specified. + + + + + + + + + Print the currently-installed version of git-crypt. + The format of the output is always "git-crypt", followed by a space, + followed by the dotted version number. + + + + + + + + + Using git-crypt + + + First, you prepare a repository to use git-crypt by running git-crypt init. + + + + Then, you specify the files to encrypt by creating a + gitattributes5 file. + Each file which you want to encrypt should be assigned the "filter=git-crypt diff=git-crypt" + attributes. For example: + + + secretfile filter=git-crypt diff=git-crypt *.key filter=git-crypt diff=git-crypt + + + Like a .gitignore file, .gitattributes files can match wildcards and + should be checked into the repository. Make sure you don't accidentally encrypt the + .gitattributes file itself (or other git files like .gitignore + or .gitmodules). Make sure your .gitattributes rules + are in place before you add sensitive files, or those files won't be encrypted! + + + + To share the repository with others (or with yourself) using GPG, run: + + + git-crypt add-gpg-user GPG_USER_ID + + + GPG_USER_ID can be a key ID, a full fingerprint, an email address, or anything + else that uniquely identifies a public key to GPG. Note: git-crypt add-gpg-user will + add and commit a GPG-encrypted key file in the .git-crypt directory of + the root of your repository. + + + + Alternatively, you can export a symmetric secret key, which you must + securely convey to collaborators (GPG is not required, and no files + are added to your repository): + + + git-crypt export-key /path/to/key + + + After cloning a repository with encrypted files, unlock with with GPG: + + + git-crypt unlock + + + Or with a symmetric key: + + + git-crypt unlock /path/to/key + + + That's all you need to do - after git-crypt is set up (either with + git-crypt init or git-crypt unlock), + you can use git normally - encryption and decryption happen transparently. + + + + + + The .gitattributes file + + + The .gitattributes file is documented in + gitattributes5. + The file pattern format is the same as the one used by .gitignore, + as documented in gitignore5, + with the exception that specifying merely a directory (e.g. "/dir/") + is not sufficient to encrypt all files beneath it. + + + + Also note that the pattern "dir/*" does not match files under + sub-directories of dir/. To encrypt an entire sub-tree dir/, place the + following in dir/.gitattributes: + + + * filter=git-crypt diff=git-crypt .gitattributes !filter !diff + + + The second pattern is essential for ensuring that .gitattributes itself + is not encrypted. + + + + + Multiple Key Support + + + In addition to the implicit default key, git-crypt supports alternative + keys which can be used to encrypt specific files and can be shared with + specific GPG users. This is useful if you want to grant different + collaborators access to different sets of files. + + + + To generate an alternative key named KEYNAME, + pass the -k KEYNAME + option to git-crypt init as follows: + + + git-crypt init -k KEYNAME + + + To encrypt a file with an alternative key, use the git-crypt-KEYNAME + filter in .gitattributes as follows: + + + secretfile filter=git-crypt-KEYNAME diff=git-crypt-KEYNAME + + + To export an alternative key or share it with a GPG user, pass the + -k KEYNAME option to + git-crypt export-key or git-crypt add-gpg-user + as follows: + + + git-crypt export-key -k KEYNAME /path/to/keyfile git-crypt add-gpg-user -k KEYNAME GPG_USER_ID + + + To unlock a repository with an alternative key, use git-crypt unlock + normally. git-crypt will automatically determine which key is being used. + + + + + + + + + + + + See Also + + git1, + gitattributes5, + git-crypt home page, + GitHub repository + + + + From 83b58eafa7480ae150afd14bc53dc76435577f2f Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Thu, 28 May 2015 21:53:24 -0700 Subject: [PATCH 29/34] Overhaul Makefile Support building the man page, but only if HAS_DOCBOOK variable set to "yes" --- Makefile | 77 ++++++++++++++++++++++++++++++++++++++------- man/man1/.gitignore | 1 + 2 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 man/man1/.gitignore diff --git a/Makefile b/Makefile index 57813ce..a3b1e87 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,9 @@ -CXX := c++ -CXXFLAGS := -Wall -pedantic -Wno-long-long -O2 -LDFLAGS := -PREFIX := /usr/local +CXXFLAGS ?= -Wall -pedantic -Wno-long-long -O2 +PREFIX ?= /usr/local +BINDIR ?= $(PREFIX)/bin +MANDIR ?= $(PREFIX)/share/man + +HAS_DOCBOOK ?= no OBJFILES = \ git-crypt.o \ @@ -17,18 +19,71 @@ OBJFILES = \ OBJFILES += crypto-openssl.o LDFLAGS += -lcrypto -all: git-crypt +DOCBOOK = xsltproc \ + --param man.output.in.separate.dir 1 \ + --stringparam man.output.base.dir man/ \ + --param man.output.subdirs.enabled 1 \ + --param man.authors.section.enabled 1 \ + --nonet \ + http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl + +all: build + +# +# Build +# +BUILD_MAN_TARGETS-yes = build-man +BUILD_MAN_TARGETS-no = +BUILD_TARGETS := build-bin $(BUILD_MAN_TARGETS-$(HAS_DOCBOOK)) + +build: $(BUILD_TARGETS) + +build-bin: git-crypt git-crypt: $(OBJFILES) $(CXX) $(CXXFLAGS) -o $@ $(OBJFILES) $(LDFLAGS) util.o: util.cpp util-unix.cpp util-win32.cpp +coprocess.o: coprocess.cpp coprocess-unix.cpp coprocess-win32.cpp -clean: - rm -f *.o git-crypt +build-man: man/man1/git-crypt.1 -install: git-crypt - install -d $(DESTDIR)$(PREFIX)/bin/ - install -m 755 git-crypt $(DESTDIR)$(PREFIX)/bin/ +man/man1/git-crypt.1: man/git-crypt.xml + $(DOCBOOK) $< -.PHONY: all clean install +# +# Clean +# +CLEAN_MAN_TARGETS-yes = clean-man +CLEAN_MAN_TARGETS-no = +CLEAN_TARGETS := clean-bin $(CLEAN_MAN_TARGETS-$(HAS_DOCBOOK)) + +clean: $(CLEAN_TARGETS) + +clean-bin: + rm -f $(OBJFILES) git-crypt + +clean-man: + rm -f man/man1/git-crypt.1 + +# +# Install +# +INSTALL_MAN_TARGETS-yes = install-man +INSTALL_MAN_TARGETS-no = +INSTALL_TARGETS := install-bin $(INSTALL_MAN_TARGETS-$(HAS_DOCBOOK)) + +install: $(INSTALL_TARGETS) + +install-bin: build-bin + install -d $(DESTDIR)$(BINDIR) + install -m 755 git-crypt $(DESTDIR)$(BINDIR)/ + +install-man: build-man + install -d $(DESTDIR)$(MANDIR)/man1 + install -m 644 man/man1/git-crypt.1 $(DESTDIR)$(MANDIR)/man1/ + +.PHONY: all \ + build build-bin build-man \ + clean clean-bin clean-man \ + install install-bin install-man diff --git a/man/man1/.gitignore b/man/man1/.gitignore new file mode 100644 index 0000000..9f42105 --- /dev/null +++ b/man/man1/.gitignore @@ -0,0 +1 @@ +git-crypt.1 From ba250b87ada45197b060550dd99976f72f653174 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Sat, 30 May 2015 17:19:15 -0700 Subject: [PATCH 30/34] Add copyright notice to Makefile --- Makefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Makefile b/Makefile index a3b1e87..d0cc328 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,9 @@ +# +# Copyright (c) 2015 Andrew Ayer +# +# See COPYING file for license information. +# + CXXFLAGS ?= -Wall -pedantic -Wno-long-long -O2 PREFIX ?= /usr/local BINDIR ?= $(PREFIX)/bin From 5dc55c3b2acbee5b6907132b457822d20995f561 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Sat, 30 May 2015 19:05:44 -0700 Subject: [PATCH 31/34] Makefile: refine man page rules --- Makefile | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index d0cc328..8dd1b5a 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,7 @@ BINDIR ?= $(PREFIX)/bin MANDIR ?= $(PREFIX)/share/man HAS_DOCBOOK ?= no +DOCBOOK_XSL ?= http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl OBJFILES = \ git-crypt.o \ @@ -25,13 +26,12 @@ OBJFILES = \ OBJFILES += crypto-openssl.o LDFLAGS += -lcrypto -DOCBOOK = xsltproc \ - --param man.output.in.separate.dir 1 \ - --stringparam man.output.base.dir man/ \ - --param man.output.subdirs.enabled 1 \ - --param man.authors.section.enabled 1 \ - --nonet \ - http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl +XSLTPROC ?= xsltproc +DOCBOOK_FLAGS += --param man.output.in.separate.dir 1 \ + --stringparam man.output.base.dir man/ \ + --param man.output.subdirs.enabled 1 \ + --param man.authors.section.enabled 1 \ + --nonet all: build @@ -55,7 +55,7 @@ coprocess.o: coprocess.cpp coprocess-unix.cpp coprocess-win32.cpp build-man: man/man1/git-crypt.1 man/man1/git-crypt.1: man/git-crypt.xml - $(DOCBOOK) $< + $(XSLTPROC) $(DOCBOOK_FLAGS) $(DOCBOOK_XSL) $< # # Clean From 9bbd39c49160ffbd665e971d15a623973226fc39 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Sat, 30 May 2015 19:05:33 -0700 Subject: [PATCH 32/34] Add instructions for building man page to INSTALL --- INSTALL | 10 ++++++++++ INSTALL.md | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/INSTALL b/INSTALL index 39af571..d6714e4 100644 --- a/INSTALL +++ b/INSTALL @@ -33,6 +33,16 @@ To install to a specific location: Or, just copy the git-crypt binary to wherever is most convenient for you. +BUILDING THE MAN PAGE + +To build and install the git-crypt(1) man page, pass HAS_DOCBOOK=yes to make: + + $ make HAS_DOCBOOK=yes + $ make HAS_DOCBOOK=yes install + +The Docbook XSLT stylesheets are required to build the man page. + + BUILDING A DEBIAN PACKAGE Debian packaging can be found in the 'debian' branch of the project diff --git a/INSTALL.md b/INSTALL.md index 5c8f592..4375896 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -33,6 +33,16 @@ To install to a specific location: Or, just copy the git-crypt binary to wherever is most convenient for you. +### Building The Man Page + +To build and install the git-crypt(1) man page, pass `HAS_DOCBOOK=yes` to make: + + make HAS_DOCBOOK=yes + make HAS_DOCBOOK=yes install + +The Docbook XSLT stylesheets are required to build the man page. + + ### Building A Debian Package Debian packaging can be found in the 'debian' branch of the project Git From f56911726a07724ee5e0fecf1c0568a974d7e312 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Sat, 30 May 2015 19:51:30 -0700 Subject: [PATCH 33/34] Makefile: refine man page rules Rename HAS_DOCBOOK option to ENABLE_MAN. Allow xsltproc to fetch the Docbook stylesheet from the Internet if it's not installed locally. This will hopefully make it easier for folks to build the man page. --- INSTALL | 10 ++++++---- INSTALL.md | 10 ++++++---- Makefile | 11 +++++------ 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/INSTALL b/INSTALL index d6714e4..41049f1 100644 --- a/INSTALL +++ b/INSTALL @@ -35,12 +35,14 @@ Or, just copy the git-crypt binary to wherever is most convenient for you. BUILDING THE MAN PAGE -To build and install the git-crypt(1) man page, pass HAS_DOCBOOK=yes to make: +To build and install the git-crypt(1) man page, pass ENABLE_MAN=yes to make: - $ make HAS_DOCBOOK=yes - $ make HAS_DOCBOOK=yes install + $ make ENABLE_MAN=yes + $ make ENABLE_MAN=yes install -The Docbook XSLT stylesheets are required to build the man page. +xsltproc is required to build the man page. Note that xsltproc will access +the Internet to retrieve its stylesheet unless the Docbook stylesheet is +installed locally and registered in the system's XML catalog. BUILDING A DEBIAN PACKAGE diff --git a/INSTALL.md b/INSTALL.md index 4375896..7fdb577 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -35,12 +35,14 @@ Or, just copy the git-crypt binary to wherever is most convenient for you. ### Building The Man Page -To build and install the git-crypt(1) man page, pass `HAS_DOCBOOK=yes` to make: +To build and install the git-crypt(1) man page, pass `ENABLE_MAN=yes` to make: - make HAS_DOCBOOK=yes - make HAS_DOCBOOK=yes install + make ENABLE_MAN=yes + make ENABLE_MAN=yes install -The Docbook XSLT stylesheets are required to build the man page. +xsltproc is required to build the man page. Note that xsltproc will access +the Internet to retrieve its stylesheet unless the Docbook stylesheet is +installed locally and registered in the system's XML catalog. ### Building A Debian Package diff --git a/Makefile b/Makefile index 8dd1b5a..bcc7516 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ PREFIX ?= /usr/local BINDIR ?= $(PREFIX)/bin MANDIR ?= $(PREFIX)/share/man -HAS_DOCBOOK ?= no +ENABLE_MAN ?= no DOCBOOK_XSL ?= http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl OBJFILES = \ @@ -30,8 +30,7 @@ XSLTPROC ?= xsltproc DOCBOOK_FLAGS += --param man.output.in.separate.dir 1 \ --stringparam man.output.base.dir man/ \ --param man.output.subdirs.enabled 1 \ - --param man.authors.section.enabled 1 \ - --nonet + --param man.authors.section.enabled 1 all: build @@ -40,7 +39,7 @@ all: build # BUILD_MAN_TARGETS-yes = build-man BUILD_MAN_TARGETS-no = -BUILD_TARGETS := build-bin $(BUILD_MAN_TARGETS-$(HAS_DOCBOOK)) +BUILD_TARGETS := build-bin $(BUILD_MAN_TARGETS-$(ENABLE_MAN)) build: $(BUILD_TARGETS) @@ -62,7 +61,7 @@ man/man1/git-crypt.1: man/git-crypt.xml # CLEAN_MAN_TARGETS-yes = clean-man CLEAN_MAN_TARGETS-no = -CLEAN_TARGETS := clean-bin $(CLEAN_MAN_TARGETS-$(HAS_DOCBOOK)) +CLEAN_TARGETS := clean-bin $(CLEAN_MAN_TARGETS-$(ENABLE_MAN)) clean: $(CLEAN_TARGETS) @@ -77,7 +76,7 @@ clean-man: # INSTALL_MAN_TARGETS-yes = install-man INSTALL_MAN_TARGETS-no = -INSTALL_TARGETS := install-bin $(INSTALL_MAN_TARGETS-$(HAS_DOCBOOK)) +INSTALL_TARGETS := install-bin $(INSTALL_MAN_TARGETS-$(ENABLE_MAN)) install: $(INSTALL_TARGETS) From bc7e55b68f6a43a9cf9e99081f860ce428835ec3 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Sat, 30 May 2015 19:53:04 -0700 Subject: [PATCH 34/34] Prepare 0.5.0 release --- NEWS | 11 +++++++++++ NEWS.md | 11 +++++++++++ README | 2 +- README.md | 4 ++-- git-crypt.hpp | 2 +- man/git-crypt.xml | 4 ++-- 6 files changed, 28 insertions(+), 6 deletions(-) diff --git a/NEWS b/NEWS index f51ea1f..d01a975 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,14 @@ +v0.5.0 (2015-05-30) + * Drastically speed up lock/unlock when used with Git 1.8.5 or newer. + * Add git-crypt(1) man page (pass ENABLE_MAN=yes to make to build). + * Add --trusted option to 'git-crypt gpg-add-user' to add user even if + GPG doesn't trust user's key. + * Improve 'git-crypt lock' usability, add --force option. + * Ignore symlinks and other non-files when running 'git-crypt status'. + * Fix compilation on old versions of Mac OS X. + * Fix GPG mode when with-fingerprint enabled in gpg.conf. + * Minor bug fixes and improvements to help/error messages. + v0.4.2 (2015-01-31) * Fix unlock and lock under Git 2.2.2 and higher. * Drop support for versions of Git older than 1.7.2. diff --git a/NEWS.md b/NEWS.md index 5da8f6b..dd8772f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,17 @@ News ==== +######v0.5.0 (2015-05-30) +* Drastically speed up lock/unlock when used with Git 1.8.5 or newer. +* Add git-crypt(1) man page (pass `ENABLE_MAN=yes` to make to build). +* Add --trusted option to `git-crypt gpg-add-user` to add user even if + GPG doesn't trust user's key. +* Improve `git-crypt lock` usability, add --force option. +* Ignore symlinks and other non-files when running `git-crypt status`. +* Fix compilation on old versions of Mac OS X. +* Fix GPG mode when with-fingerprint enabled in gpg.conf. +* Minor bug fixes and improvements to help/error messages. + ######v0.4.2 (2015-01-31) * Fix unlock and lock under Git 2.2.2 and higher. * Drop support for versions of Git older than 1.7.2. diff --git a/README b/README index 5d2477e..fd982ac 100644 --- a/README +++ b/README @@ -69,7 +69,7 @@ encryption and decryption happen transparently. CURRENT STATUS -The latest version of git-crypt is 0.4.2, released on 2015-01-31. +The latest version of git-crypt is 0.5.0, released on 2015-05-30. git-crypt aims to be bug-free and reliable, meaning it shouldn't crash, malfunction, or expose your confidential data. However, it has not yet reached maturity, meaning it is not as documented, diff --git a/README.md b/README.md index 2fffbef..1aeccc8 100644 --- a/README.md +++ b/README.md @@ -70,8 +70,8 @@ encryption and decryption happen transparently. Current Status -------------- -The latest version of git-crypt is [0.4.2](NEWS.md), released on -2015-01-31. git-crypt aims to be bug-free and reliable, meaning it +The latest version of git-crypt is [0.5.0](NEWS.md), released on +2015-05-30. git-crypt aims to be bug-free and reliable, meaning it shouldn't crash, malfunction, or expose your confidential data. However, it has not yet reached maturity, meaning it is not as documented, featureful, or easy-to-use as it should be. Additionally, diff --git a/git-crypt.hpp b/git-crypt.hpp index 78b8196..bb4beaa 100644 --- a/git-crypt.hpp +++ b/git-crypt.hpp @@ -31,7 +31,7 @@ #ifndef GIT_CRYPT_GIT_CRYPT_HPP #define GIT_CRYPT_GIT_CRYPT_HPP -#define VERSION "0.4.2" +#define VERSION "0.5.0" extern const char* argv0; // initialized in main() to argv[0] diff --git a/man/git-crypt.xml b/man/git-crypt.xml index a444368..7a20569 100644 --- a/man/git-crypt.xml +++ b/man/git-crypt.xml @@ -7,8 +7,8 @@ --> git-crypt - 2015-05-17 - git-crypt 0.4.3 + 2015-05-30 + git-crypt 0.5.0 Andrew Ayer