6 Commits
0.4.1 ... 0.4.2

Author SHA1 Message Date
Andrew Ayer
1ca8f89602 Prepare for 0.4.2 release 2015-01-31 19:20:05 -08:00
Andrew Ayer
5fd36a7ac5 Increase minimum supported Git version to 1.7.2
Previously, git-crypt claimed to support Git as old as 1.6.0 (albeit
with degraded operation).  However, this has not been true for some time,
since Git 1.6.0 does not support the --porcelain option to `git status`.

Since Git 1.7.2 was the first version of Git to support filters with
`git blame`, was released almost five years ago (in July 2010), and is
even in Debian Squeeze, it seems like a good minimum version to require.
2015-01-27 21:26:51 -08:00
Andrew Ayer
d5670c9552 Force Git to check out files by touching their mtimes
Starting with Git 2.2.2, `git checkout -f HEAD` no longer checks out
files if their mtimes haven't changed.  This causes files to remain
encrypted in the work tree after running `git-crypt unlock`, and to
remain decrypted after running `git-crypt lock`'.

To fix this, git-crypt now figures out what files are encrypted (by
checking `git check-attr` on every file output by `git ls-files`),
touches those files, and then runs `git checkout` on them.
2015-01-27 21:15:07 -08:00
Andrew Ayer
2d2053296f Fix placement of quotes in an error message 2015-01-27 21:06:29 -08:00
Andrew Ayer
216aa27009 Add helper function to get attribute name for a given key 2015-01-27 21:04:58 -08:00
Andrew Ayer
02c52ab21a Disable message about unimplemented ls-gpg-users command 2015-01-27 21:04:22 -08:00
8 changed files with 97 additions and 85 deletions

View File

@@ -2,10 +2,8 @@ DEPENDENCIES
To use git-crypt, you need: To use git-crypt, you need:
* Git 1.6.0 or newer * Git 1.7.2 or newer
* OpenSSL * OpenSSL
* For decrypted git diff output, Git 1.6.1 or newer
* For decrypted git blame output, Git 1.7.2 or newer
To build git-crypt, you need a C++ compiler and OpenSSL development To build git-crypt, you need a C++ compiler and OpenSSL development
headers. headers.

View File

@@ -3,10 +3,8 @@ Dependencies
To use git-crypt, you need: To use git-crypt, you need:
* Git 1.6.0 or newer * Git 1.7.2 or newer
* OpenSSL * OpenSSL
* For decrypted git diff output, Git 1.6.1 or newer
* For decrypted git blame output, Git 1.7.2 or newer
To build git-crypt, you need a C++ compiler and OpenSSL development To build git-crypt, you need a C++ compiler and OpenSSL development
headers. headers.

5
NEWS
View File

@@ -1,3 +1,8 @@
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.
* Minor improvements to some help/error messages.
v0.4.1 (2015-01-08) v0.4.1 (2015-01-08)
* Important usability fix to ensure that the .git-crypt directory * Important usability fix to ensure that the .git-crypt directory
can't be encrypted by accident (see RELEASE_NOTES-0.4.1.md for can't be encrypted by accident (see RELEASE_NOTES-0.4.1.md for

View File

@@ -1,6 +1,11 @@
News News
==== ====
######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.
* Minor improvements to some help/error messages.
######v0.4.1 (2015-01-08) ######v0.4.1 (2015-01-08)
* Important usability fix to ensure that the .git-crypt directory * Important usability fix to ensure that the .git-crypt directory
can't be encrypted by accident (see can't be encrypted by accident (see

2
README
View File

@@ -66,7 +66,7 @@ encryption and decryption happen transparently.
CURRENT STATUS CURRENT STATUS
The latest version of git-crypt is 0.4.1, released on 2015-01-08. The latest version of git-crypt is 0.4.2, released on 2015-01-31.
git-crypt aims to be bug-free and reliable, meaning it shouldn't git-crypt aims to be bug-free and reliable, meaning it shouldn't
crash, malfunction, or expose your confidential data. However, crash, malfunction, or expose your confidential data. However,
it has not yet reached maturity, meaning it is not as documented, it has not yet reached maturity, meaning it is not as documented,

View File

@@ -67,8 +67,8 @@ encryption and decryption happen transparently.
Current Status Current Status
-------------- --------------
The latest version of git-crypt is [0.4.1](RELEASE_NOTES-0.4.1.md), released on The latest version of git-crypt is [0.4.2](NEWS.md), released on
2015-01-08. git-crypt aims to be bug-free and reliable, meaning it 2015-01-31. git-crypt aims to be bug-free and reliable, meaning it
shouldn't crash, malfunction, or expose your confidential data. shouldn't crash, malfunction, or expose your confidential data.
However, it has not yet reached maturity, meaning it is not as However, it has not yet reached maturity, meaning it is not as
documented, featureful, or easy-to-use as it should be. Additionally, documented, featureful, or easy-to-use as it should be. Additionally,

View File

@@ -49,6 +49,17 @@
#include <errno.h> #include <errno.h>
#include <vector> #include <vector>
static std::string attribute_name (const char* key_name)
{
if (key_name) {
// named key
return std::string("git-crypt-") + key_name;
} else {
// default key
return "git-crypt";
}
}
static void git_config (const std::string& name, const std::string& value) static void git_config (const std::string& name, const std::string& value)
{ {
std::vector<std::string> command; std::vector<std::string> command;
@@ -99,31 +110,20 @@ static void configure_git_filters (const char* key_name)
static void unconfigure_git_filters (const char* key_name) static void unconfigure_git_filters (const char* key_name)
{ {
// unconfigure the git-crypt filters // unconfigure the git-crypt filters
if (key_name) { git_unconfig("filter." + attribute_name(key_name));
// named key git_unconfig("diff." + attribute_name(key_name));
git_unconfig(std::string("filter.git-crypt-") + key_name);
git_unconfig(std::string("diff.git-crypt-") + key_name);
} else {
// default key
git_unconfig("filter.git-crypt");
git_unconfig("diff.git-crypt");
}
} }
static bool git_checkout_head (const std::string& top_dir) static bool git_checkout (const std::vector<std::string>& paths)
{ {
std::vector<std::string> command; std::vector<std::string> command;
command.push_back("git"); command.push_back("git");
command.push_back("checkout"); command.push_back("checkout");
command.push_back("-f");
command.push_back("HEAD");
command.push_back("--"); command.push_back("--");
if (top_dir.empty()) { for (std::vector<std::string>::const_iterator path(paths.begin()); path != paths.end(); ++path) {
command.push_back("."); command.push_back(*path);
} else {
command.push_back(top_dir);
} }
if (!successful_exit(exec_command(command))) { if (!successful_exit(exec_command(command))) {
@@ -256,18 +256,6 @@ static void get_git_status (std::ostream& output)
} }
} }
static bool check_if_head_exists ()
{
// git rev-parse HEAD
std::vector<std::string> command;
command.push_back("git");
command.push_back("rev-parse");
command.push_back("HEAD");
std::stringstream output;
return successful_exit(exec_command(command, output));
}
// returns filter and diff attributes as a pair // returns filter and diff attributes as a pair
static std::pair<std::string, std::string> get_file_attributes (const std::string& filename) static std::pair<std::string, std::string> get_file_attributes (const std::string& filename)
{ {
@@ -368,6 +356,35 @@ static bool check_if_file_is_encrypted (const std::string& filename)
return check_if_blob_is_encrypted(object_id); return check_if_blob_is_encrypted(object_id);
} }
static void get_encrypted_files (std::vector<std::string>& files, const char* key_name)
{
// git ls-files -cz -- path_to_top
std::vector<std::string> command;
command.push_back("git");
command.push_back("ls-files");
command.push_back("-cz");
command.push_back("--");
const std::string path_to_top(get_path_to_top());
if (!path_to_top.empty()) {
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?");
}
while (output.peek() != -1) {
std::string filename;
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)) {
files.push_back(filename);
}
}
}
static void load_key (Key_file& key_file, const char* key_name, const char* key_path =0, const char* legacy_path =0) static void load_key (Key_file& key_file, const char* key_name, const char* key_path =0, const char* legacy_path =0)
{ {
if (legacy_path) { if (legacy_path) {
@@ -781,25 +798,18 @@ void help_unlock (std::ostream& out)
} }
int unlock (int argc, const char** argv) int unlock (int argc, const char** argv)
{ {
// 0. Make sure working directory is clean (ignoring untracked files) // 1. Make sure working directory is clean (ignoring untracked files)
// We do this because we run 'git checkout -f HEAD' later and we don't // We do this because we check out files later, and we don't want the
// want the user to lose any changes. 'git checkout -f HEAD' doesn't touch // user to lose any changes. (TODO: only care if encrypted files are
// untracked files so it's safe to ignore those. // modified, since we only check out encrypted files)
// Running 'git status' also serves as a check that the Git repo is accessible. // Running 'git status' also serves as a check that the Git repo is accessible.
std::stringstream status_output; std::stringstream status_output;
get_git_status(status_output); get_git_status(status_output);
if (status_output.peek() != -1) {
// 1. Check to see if HEAD exists. See below why we do this.
bool head_exists = check_if_head_exists();
if (status_output.peek() != -1 && head_exists) {
// We only care that the working directory is dirty if HEAD exists.
// If HEAD doesn't exist, we won't be resetting to it (see below) so
// it doesn't matter that the working directory is dirty.
std::clog << "Error: Working directory not clean." << std::endl; std::clog << "Error: Working directory not clean." << std::endl;
std::clog << "Please commit your changes or 'git stash' them before running 'git-crypt' unlock." << std::endl; std::clog << "Please commit your changes or 'git stash' them before running 'git-crypt unlock'." << std::endl;
return 1; return 1;
} }
@@ -850,13 +860,14 @@ int unlock (int argc, const char** argv)
if (!decrypt_repo_keys(key_files, 0, gpg_secret_keys, repo_keys_path)) { if (!decrypt_repo_keys(key_files, 0, gpg_secret_keys, repo_keys_path)) {
std::clog << "Error: no GPG secret key available to unlock this repository." << std::endl; std::clog << "Error: no GPG secret key available to unlock this repository." << std::endl;
std::clog << "To unlock with a shared symmetric key instead, specify the path to the symmetric key as an argument to 'git-crypt unlock'." << std::endl; std::clog << "To unlock with a shared symmetric key instead, specify the path to the symmetric key as an argument to 'git-crypt unlock'." << std::endl;
std::clog << "To see a list of GPG keys authorized to unlock this repository, run 'git-crypt ls-collabs'." << std::endl; // TODO std::clog << "To see a list of GPG keys authorized to unlock this repository, run 'git-crypt ls-gpg-users'." << std::endl;
return 1; return 1;
} }
} }
// 4. Install the key(s) and configure the git filters // 4. Install the key(s) and configure the git filters
std::vector<std::string> encrypted_files;
for (std::vector<Key_file>::iterator key_file(key_files.begin()); key_file != key_files.end(); ++key_file) { for (std::vector<Key_file>::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())); std::string internal_key_path(get_internal_key_path(key_file->get_key_name()));
// TODO: croak if internal_key_path already exists??? // TODO: croak if internal_key_path already exists???
@@ -867,18 +878,18 @@ int unlock (int argc, const char** argv)
} }
configure_git_filters(key_file->get_key_name()); configure_git_filters(key_file->get_key_name());
get_encrypted_files(encrypted_files, key_file->get_key_name());
} }
// 5. Do a force checkout so any files that were previously checked out encrypted // 5. Check out the files that are currently encrypted.
// will now be checked out decrypted. // Git won't check out a file if its mtime hasn't changed, so touch every file first.
// If HEAD doesn't exist (perhaps because this repo doesn't have any files yet) for (std::vector<std::string>::const_iterator file(encrypted_files.begin()); file != encrypted_files.end(); ++file) {
// just skip the checkout. touch_file(*file);
if (head_exists) { }
if (!git_checkout_head(path_to_top)) { if (!git_checkout(encrypted_files)) {
std::clog << "Error: 'git checkout' failed" << std::endl; std::clog << "Error: 'git checkout' failed" << std::endl;
std::clog << "git-crypt has been set up but existing encrypted files have not been decrypted" << std::endl; std::clog << "git-crypt has been set up but existing encrypted files have not been decrypted" << std::endl;
return 1; return 1;
}
} }
return 0; return 0;
@@ -916,25 +927,18 @@ int lock (int argc, const char** argv)
return 2; return 2;
} }
// 0. Make sure working directory is clean (ignoring untracked files) // 1. Make sure working directory is clean (ignoring untracked files)
// We do this because we run 'git checkout -f HEAD' later and we don't // We do this because we check out files later, and we don't want the
// want the user to lose any changes. 'git checkout -f HEAD' doesn't touch // user to lose any changes. (TODO: only care if encrypted files are
// untracked files so it's safe to ignore those. // modified, since we only check out encrypted files)
// Running 'git status' also serves as a check that the Git repo is accessible. // Running 'git status' also serves as a check that the Git repo is accessible.
std::stringstream status_output; std::stringstream status_output;
get_git_status(status_output); get_git_status(status_output);
if (status_output.peek() != -1) {
// 1. Check to see if HEAD exists. See below why we do this.
bool head_exists = check_if_head_exists();
if (status_output.peek() != -1 && head_exists) {
// We only care that the working directory is dirty if HEAD exists.
// If HEAD doesn't exist, we won't be resetting to it (see below) so
// it doesn't matter that the working directory is dirty.
std::clog << "Error: Working directory not clean." << std::endl; 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 << "Please commit your changes or 'git stash' them before running 'git-crypt lock'." << std::endl;
return 1; return 1;
} }
@@ -944,6 +948,7 @@ int lock (int argc, const char** argv)
std::string path_to_top(get_path_to_top()); std::string path_to_top(get_path_to_top());
// 3. unconfigure the git filters and remove decrypted keys // 3. unconfigure the git filters and remove decrypted keys
std::vector<std::string> encrypted_files;
if (all_keys) { if (all_keys) {
// unconfigure for all keys // unconfigure for all keys
std::vector<std::string> dirents = get_directory_contents(get_internal_keys_path().c_str()); std::vector<std::string> dirents = get_directory_contents(get_internal_keys_path().c_str());
@@ -952,6 +957,7 @@ int lock (int argc, const char** argv)
const char* this_key_name = (*dirent == "default" ? 0 : dirent->c_str()); const char* this_key_name = (*dirent == "default" ? 0 : dirent->c_str());
remove_file(get_internal_key_path(this_key_name)); remove_file(get_internal_key_path(this_key_name));
unconfigure_git_filters(this_key_name); unconfigure_git_filters(this_key_name);
get_encrypted_files(encrypted_files, this_key_name);
} }
} else { } else {
// just handle the given key // just handle the given key
@@ -967,18 +973,18 @@ int lock (int argc, const char** argv)
remove_file(internal_key_path); remove_file(internal_key_path);
unconfigure_git_filters(key_name); unconfigure_git_filters(key_name);
get_encrypted_files(encrypted_files, key_name);
} }
// 4. Do a force checkout so any files that were previously checked out decrypted // 4. Check out the files that are currently decrypted but should be encrypted.
// will now be checked out encrypted. // Git won't check out a file if its mtime hasn't changed, so touch every file first.
// If HEAD doesn't exist (perhaps because this repo doesn't have any files yet) for (std::vector<std::string>::const_iterator file(encrypted_files.begin()); file != encrypted_files.end(); ++file) {
// just skip the checkout. touch_file(*file);
if (head_exists) { }
if (!git_checkout_head(path_to_top)) { if (!git_checkout(encrypted_files)) {
std::clog << "Error: 'git checkout' failed" << std::endl; std::clog << "Error: 'git checkout' failed" << std::endl;
std::clog << "git-crypt has been locked but up but existing decrypted files have not been encrypted" << std::endl; std::clog << "git-crypt has been locked but up but existing decrypted files have not been encrypted" << std::endl;
return 1; return 1;
}
} }
return 0; return 0;

View File

@@ -31,7 +31,7 @@
#ifndef GIT_CRYPT_GIT_CRYPT_HPP #ifndef GIT_CRYPT_GIT_CRYPT_HPP
#define GIT_CRYPT_GIT_CRYPT_HPP #define GIT_CRYPT_GIT_CRYPT_HPP
#define VERSION "0.4.1" #define VERSION "0.4.2"
extern const char* argv0; // initialized in main() to argv[0] extern const char* argv0; // initialized in main() to argv[0]