12 Commits
0.4 ... 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
Andrew Ayer
849401d733 Update for git-crypt 0.4.1 2015-01-07 20:23:28 -08:00
Andrew Ayer
12881f65fd Add 'git-crypt version' command 2015-01-07 20:23:07 -08:00
Wael M. Nasreddine
280bd43ac7 Makefile: The install target should depend git-crypt.
Signed-off-by: Andrew Ayer <agwa@andrewayer.name>
2015-01-07 20:06:45 -08:00
Andrew Ayer
b7c608da25 Add .gitattributes file to .git-crypt dir to prevent encryption
Previously, if you had a .gitattributes file in the root of your
repository that matched `*`, the files under .git-crypt would also be
encrypted, rendering the repository un-decryptable, unless you explicitly
excluded the .git-crypt directory, which was easy to overlook.

Now, `git-crypt add-gpg-user` automatically adds a .gitattributes file
to the .git-crypt directory to prevent its encryption.

IMPORTANT: If you are currently using GPG mode to encrypt an entire
repository, it is strongly advised that you upgrade git-crypt and then
do the following to ensure that the files inside .git-crypt are stored
properly:

 1. Remove existing key files: `rm .git-crypt/keys/*/0/*`
 2. Re-add GPG user(s): `git-crypt add-gpg-user GPG_USER_ID ...`
2015-01-02 10:35:57 -08:00
Andrew Ayer
9cb1ad3c33 Add some helpers for getting paths to state directories 2015-01-02 10:30:47 -08:00
Andrew Ayer
1b1715b5ec README: use https URLs for mailing lists 2014-11-26 09:42:10 -08:00
11 changed files with 190 additions and 95 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.

View File

@@ -25,7 +25,7 @@ util.o: util.cpp util-unix.cpp util-win32.cpp
clean: clean:
rm -f *.o git-crypt rm -f *.o git-crypt
install: install: git-crypt
install -m 755 git-crypt $(DESTDIR)$(PREFIX)/bin/ install -m 755 git-crypt $(DESTDIR)$(PREFIX)/bin/
.PHONY: all clean install .PHONY: all clean install

10
NEWS
View File

@@ -1,3 +1,13 @@
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)
* Important usability fix to ensure that the .git-crypt directory
can't be encrypted by accident (see RELEASE_NOTES-0.4.1.md for
more information).
v0.4 (2014-11-16) v0.4 (2014-11-16)
(See RELEASE_NOTES-0.4.md for important details.) (See RELEASE_NOTES-0.4.md for important details.)
* Add optional GPG support: GPG can be used to share the repository * Add optional GPG support: GPG can be used to share the repository

10
NEWS.md
View File

@@ -1,6 +1,16 @@
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)
* Important usability fix to ensure that the .git-crypt directory
can't be encrypted by accident (see
[the release notes](RELEASE_NOTES-0.4.1.md) for more information).
######v0.4 (2014-11-16) ######v0.4 (2014-11-16)
(See [the release notes](RELEASE_NOTES-0.4.md) for important details.) (See [the release notes](RELEASE_NOTES-0.4.md) for important details.)
* Add optional GPG support: GPG can be used to share the repository * Add optional GPG support: GPG can be used to share the repository

6
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, released on 2014-11-16. 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,
@@ -145,5 +145,5 @@ MAILING LISTS
To stay abreast of, and provide input to, git-crypt development, consider To stay abreast of, and provide input to, git-crypt development, consider
subscribing to one or both of our mailing lists: subscribing to one or both of our mailing lists:
Announcements: http://lists.cloudmutt.com/mailman/listinfo/git-crypt-announce Announcements: https://lists.cloudmutt.com/mailman/listinfo/git-crypt-announce
Discussion: http://lists.cloudmutt.com/mailman/listinfo/git-crypt-discuss Discussion: https://lists.cloudmutt.com/mailman/listinfo/git-crypt-discuss

View File

@@ -67,8 +67,8 @@ encryption and decryption happen transparently.
Current Status Current Status
-------------- --------------
The latest version of git-crypt is [0.4](RELEASE_NOTES-0.4.md), released on The latest version of git-crypt is [0.4.2](NEWS.md), released on
2014-11-16. 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,
@@ -147,5 +147,5 @@ Mailing Lists
To stay abreast of, and provide input to, git-crypt development, To stay abreast of, and provide input to, git-crypt development,
consider subscribing to one or both of our mailing lists: consider subscribing to one or both of our mailing lists:
* [Announcements](http://lists.cloudmutt.com/mailman/listinfo/git-crypt-announce) * [Announcements](https://lists.cloudmutt.com/mailman/listinfo/git-crypt-announce)
* [Discussion](http://lists.cloudmutt.com/mailman/listinfo/git-crypt-discuss) * [Discussion](https://lists.cloudmutt.com/mailman/listinfo/git-crypt-discuss)

21
RELEASE_NOTES-0.4.1.md Normal file
View File

@@ -0,0 +1,21 @@
git-crypt 0.4.1 is a bugfix-only release that contains an important
usability fix for users who use GPG mode to encrypt an entire repository.
Previously, if you used a '*' pattern in the top-level .gitattributes
file, and you did not explicitly add a pattern to exclude the .git-crypt
directory, the files contained therein would be encrypted, rendering
the repository impossible to unlock with GPG.
git-crypt now adds a .gitattributes file to the .git-crypt directory
to prevent its contents from being encrypted, regardless of patterns in
the top-level .gitattributes.
If you are using git-crypt in GPG mode to encrypt an entire repository,
and you do not already have a .gitattributes pattern to exclude the
.git-crypt directory, you are strongly advised to upgrade. After
upgrading, you should do the following in each of your repositories to
ensure that the information inside .git-crypt is properly stored:
1. Remove existing key files: `rm .git-crypt/keys/*/0/*`
2. Re-add GPG user(s): `git-crypt add-gpg-user GPG_USER_ID ...`

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))) {
@@ -146,7 +146,7 @@ static void validate_key_name_or_throw (const char* key_name)
} }
} }
static std::string get_internal_keys_path () static std::string get_internal_state_path ()
{ {
// git rev-parse --git-dir // git rev-parse --git-dir
std::vector<std::string> command; std::vector<std::string> command;
@@ -162,11 +162,21 @@ static std::string get_internal_keys_path ()
std::string path; std::string path;
std::getline(output, path); std::getline(output, path);
path += "/git-crypt/keys"; path += "/git-crypt";
return path; return path;
} }
static std::string get_internal_keys_path (const std::string& internal_state_path)
{
return internal_state_path + "/keys";
}
static std::string get_internal_keys_path ()
{
return get_internal_keys_path(get_internal_state_path());
}
static std::string get_internal_key_path (const char* key_name) static std::string get_internal_key_path (const char* key_name)
{ {
std::string path(get_internal_keys_path()); std::string path(get_internal_keys_path());
@@ -176,7 +186,7 @@ static std::string get_internal_key_path (const char* key_name)
return path; return path;
} }
static std::string get_repo_keys_path () static std::string get_repo_state_path ()
{ {
// git rev-parse --show-toplevel // git rev-parse --show-toplevel
std::vector<std::string> command; std::vector<std::string> command;
@@ -198,10 +208,20 @@ static std::string get_repo_keys_path ()
throw Error("Could not determine Git working tree - is this a non-bare repo?"); throw Error("Could not determine Git working tree - is this a non-bare repo?");
} }
path += "/.git-crypt/keys"; path += "/.git-crypt";
return path; return path;
} }
static std::string get_repo_keys_path (const std::string& repo_state_path)
{
return repo_state_path + "/keys";
}
static std::string get_repo_keys_path ()
{
return get_repo_keys_path(get_repo_state_path());
}
static std::string get_path_to_top () static std::string get_path_to_top ()
{ {
// git rev-parse --show-cdup // git rev-parse --show-cdup
@@ -236,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)
{ {
@@ -348,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) {
@@ -761,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;
} }
@@ -830,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???
@@ -847,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;
@@ -896,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;
} }
@@ -924,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());
@@ -932,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
@@ -947,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;
@@ -1015,10 +1041,23 @@ int add_gpg_user (int argc, const char** argv)
return 1; return 1;
} }
std::string keys_path(get_repo_keys_path()); const std::string state_path(get_repo_state_path());
std::vector<std::string> new_files; std::vector<std::string> new_files;
encrypt_repo_key(key_name, *key, collab_keys, keys_path, &new_files); encrypt_repo_key(key_name, *key, collab_keys, get_repo_keys_path(state_path), &new_files);
// Add a .gitatributes file to the repo state directory to prevent files in it from being encrypted.
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());
state_gitattributes_file << "* !filter !diff\n";
state_gitattributes_file.close();
if (!state_gitattributes_file) {
std::clog << "Error: unable to write " << state_gitattributes_path << std::endl;
return 1;
}
new_files.push_back(state_gitattributes_path);
}
// add/commit the new files // add/commit the new files
if (!new_files.empty()) { if (!new_files.empty()) {

View File

@@ -78,6 +78,11 @@ static void print_usage (std::ostream& out)
out << "See 'git-crypt help COMMAND' for more information on a specific command." << std::endl; out << "See 'git-crypt help COMMAND' for more information on a specific command." << std::endl;
} }
static void print_version (std::ostream& out)
{
out << "git-crypt " << VERSION << std::endl;
}
static bool help_for_command (const char* command, std::ostream& out) static bool help_for_command (const char* command, std::ostream& out)
{ {
if (std::strcmp(command, "init") == 0) { if (std::strcmp(command, "init") == 0) {
@@ -121,6 +126,12 @@ static int help (int argc, const char** argv)
return 0; return 0;
} }
static int version (int argc, const char** argv)
{
print_version(std::cout);
return 0;
}
int main (int argc, const char** argv) int main (int argc, const char** argv)
try { try {
@@ -141,6 +152,9 @@ try {
if (std::strcmp(argv[arg_index], "--help") == 0) { if (std::strcmp(argv[arg_index], "--help") == 0) {
print_usage(std::clog); print_usage(std::clog);
return 0; return 0;
} else if (std::strcmp(argv[arg_index], "--version") == 0) {
print_version(std::clog);
return 0;
} else if (std::strcmp(argv[arg_index], "--") == 0) { } else if (std::strcmp(argv[arg_index], "--") == 0) {
++arg_index; ++arg_index;
break; break;
@@ -171,6 +185,9 @@ try {
if (std::strcmp(command, "help") == 0) { if (std::strcmp(command, "help") == 0) {
return help(argc, argv); return help(argc, argv);
} }
if (std::strcmp(command, "version") == 0) {
return version(argc, argv);
}
if (std::strcmp(command, "init") == 0) { if (std::strcmp(command, "init") == 0) {
return init(argc, argv); return init(argc, argv);
} }

View File

@@ -31,6 +31,8 @@
#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.2"
extern const char* argv0; // initialized in main() to argv[0] extern const char* argv0; // initialized in main() to argv[0]
#endif #endif