mirror of
https://gitlab.alpinelinux.org/alpine/aports.git
synced 2025-07-21 18:25:41 +03:00
365 lines
15 KiB
Diff
365 lines
15 KiB
Diff
From b6f4f33ba969cfd44cdd9a5df2048ad5b6330fb0 Mon Sep 17 00:00:00 2001
|
|
From: Jakub Jirutka <jakub@jirutka.cz>
|
|
Date: Sun, 28 Jul 2019 17:14:09 +0200
|
|
Subject: [PATCH 1/2] Change clean() and smudge() functions to accept in/out
|
|
stream
|
|
|
|
This is a preparation for the merge command.
|
|
|
|
I've extracted this change from #107.
|
|
|
|
Co-Authored-By: Shlomo Shachar <shlomo.shachar@binatix.com>
|
|
Patch-Source: https://github.com/AGWA/git-crypt/pull/180
|
|
---
|
|
commands.cpp | 38 +++++++++++++++++++-------------------
|
|
commands.hpp | 5 +++--
|
|
2 files changed, 22 insertions(+), 21 deletions(-)
|
|
|
|
diff --git a/commands.cpp b/commands.cpp
|
|
index 81c401d..28bcdee 100644
|
|
--- a/commands.cpp
|
|
+++ b/commands.cpp
|
|
@@ -166,11 +166,16 @@ static void configure_git_filters (const char* key_name)
|
|
git_config(std::string("filter.git-crypt-") + key_name + ".required", "true");
|
|
git_config(std::string("diff.git-crypt-") + key_name + ".textconv",
|
|
escaped_git_crypt_path + " diff --key-name=" + key_name);
|
|
+ git_config(std::string("merge.git-crypt-") + key_name + ".name", "git-crypt merge driver");
|
|
+ git_config(std::string("merge.git-crypt-") + key_name + ".driver",
|
|
+ escaped_git_crypt_path + " merge --key-name=" + key_name + " %A %O %B %L");
|
|
} else {
|
|
git_config("filter.git-crypt.smudge", escaped_git_crypt_path + " smudge");
|
|
git_config("filter.git-crypt.clean", escaped_git_crypt_path + " clean");
|
|
git_config("filter.git-crypt.required", "true");
|
|
git_config("diff.git-crypt.textconv", escaped_git_crypt_path + " diff");
|
|
+ git_config("merge.git-crypt.name", "git-crypt merge driver");
|
|
+ git_config("merge.git-crypt.driver", escaped_git_crypt_path + " merge %A %O %B %L");
|
|
}
|
|
}
|
|
|
|
@@ -187,6 +192,12 @@ static void deconfigure_git_filters (const char* key_name)
|
|
if (git_has_config("diff." + attribute_name(key_name) + ".textconv")) {
|
|
git_deconfig("diff." + attribute_name(key_name));
|
|
}
|
|
+
|
|
+ if (git_has_config("merge." + attribute_name(key_name) + ".name") ||
|
|
+ git_has_config("merge." + attribute_name(key_name) + ".driver")) {
|
|
+
|
|
+ git_deconfig("merge." + attribute_name(key_name));
|
|
+ }
|
|
}
|
|
|
|
static bool git_checkout_batch (std::vector<std::string>::const_iterator paths_begin, std::vector<std::string>::const_iterator paths_end)
|
|
@@ -712,8 +723,8 @@ static int parse_plumbing_options (const char** key_name, const char** key_file,
|
|
return parse_options(options, argc, argv);
|
|
}
|
|
|
|
-// Encrypt contents of stdin and write to stdout
|
|
-int clean (int argc, const char** argv)
|
|
+// Encrypt contents of &in and write to &out
|
|
+int clean (int argc, const char** argv, std::istream& in, std::ostream& out)
|
|
{
|
|
const char* key_name = 0;
|
|
const char* key_path = 0;
|
|
@@ -746,10 +757,10 @@ int clean (int argc, const char** argv)
|
|
|
|
char buffer[1024];
|
|
|
|
- while (std::cin && file_size < Aes_ctr_encryptor::MAX_CRYPT_BYTES) {
|
|
- std::cin.read(buffer, sizeof(buffer));
|
|
+ while (in && file_size < Aes_ctr_encryptor::MAX_CRYPT_BYTES) {
|
|
+ in.read(buffer, sizeof(buffer));
|
|
|
|
- const size_t bytes_read = std::cin.gcount();
|
|
+ const size_t bytes_read = in.gcount();
|
|
|
|
hmac.add(reinterpret_cast<unsigned char*>(buffer), bytes_read);
|
|
file_size += bytes_read;
|
|
@@ -797,8 +808,8 @@ int clean (int argc, const char** argv)
|
|
hmac.get(digest);
|
|
|
|
// Write a header that...
|
|
- std::cout.write("\0GITCRYPT\0", 10); // ...identifies this as an encrypted file
|
|
- std::cout.write(reinterpret_cast<char*>(digest), Aes_ctr_encryptor::NONCE_LEN); // ...includes the nonce
|
|
+ out.write("\0GITCRYPT\0", 10); // ...identifies this as an encrypted file
|
|
+ out.write(reinterpret_cast<char*>(digest), Aes_ctr_encryptor::NONCE_LEN); // ...includes the nonce
|
|
|
|
// Now encrypt the file and write to stdout
|
|
Aes_ctr_encryptor aes(key->aes_key, digest);
|
|
@@ -809,7 +820,7 @@ int clean (int argc, const char** argv)
|
|
while (file_data_len > 0) {
|
|
const size_t buffer_len = std::min(sizeof(buffer), file_data_len);
|
|
aes.process(file_data, reinterpret_cast<unsigned char*>(buffer), buffer_len);
|
|
- std::cout.write(buffer, buffer_len);
|
|
+ out.write(buffer, buffer_len);
|
|
file_data += buffer_len;
|
|
file_data_len -= buffer_len;
|
|
}
|
|
@@ -825,14 +836,14 @@ int clean (int argc, const char** argv)
|
|
aes.process(reinterpret_cast<unsigned char*>(buffer),
|
|
reinterpret_cast<unsigned char*>(buffer),
|
|
buffer_len);
|
|
- std::cout.write(buffer, buffer_len);
|
|
+ out.write(buffer, buffer_len);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
-static int decrypt_file_to_stdout (const Key_file& key_file, const unsigned char* header, std::istream& in)
|
|
+static int decrypt_file_to_stream (const Key_file& key_file, const unsigned char* header, std::istream& in, std::ostream& out = std::cout)
|
|
{
|
|
const unsigned char* nonce = header + 10;
|
|
uint32_t key_version = 0; // TODO: get the version from the file header
|
|
@@ -850,7 +861,7 @@ static int decrypt_file_to_stdout (const Key_file& key_file, const unsigned char
|
|
in.read(reinterpret_cast<char*>(buffer), sizeof(buffer));
|
|
aes.process(buffer, buffer, in.gcount());
|
|
hmac.add(buffer, in.gcount());
|
|
- std::cout.write(reinterpret_cast<char*>(buffer), in.gcount());
|
|
+ out.write(reinterpret_cast<char*>(buffer), in.gcount());
|
|
}
|
|
|
|
unsigned char digest[Hmac_sha1_state::LEN];
|
|
@@ -866,8 +877,8 @@ static int decrypt_file_to_stdout (const Key_file& key_file, const unsigned char
|
|
return 0;
|
|
}
|
|
|
|
-// Decrypt contents of stdin and write to stdout
|
|
-int smudge (int argc, const char** argv)
|
|
+// Decrypt contents of &in and write to &out
|
|
+int smudge (int argc, const char** argv, std::istream& in, std::ostream& out)
|
|
{
|
|
const char* key_name = 0;
|
|
const char* key_path = 0;
|
|
@@ -886,8 +897,8 @@ int smudge (int argc, const char** argv)
|
|
|
|
// Read the header to get the nonce and make sure it's actually encrypted
|
|
unsigned char header[10 + Aes_ctr_decryptor::NONCE_LEN];
|
|
- std::cin.read(reinterpret_cast<char*>(header), sizeof(header));
|
|
- if (std::cin.gcount() != sizeof(header) || std::memcmp(header, "\0GITCRYPT\0", 10) != 0) {
|
|
+ in.read(reinterpret_cast<char*>(header), sizeof(header));
|
|
+ if (in.gcount() != sizeof(header) || std::memcmp(header, "\0GITCRYPT\0", 10) != 0) {
|
|
// File not encrypted - just copy it out to stdout
|
|
std::clog << "git-crypt: Warning: file not encrypted" << std::endl;
|
|
std::clog << "git-crypt: Run 'git-crypt status' to make sure all files are properly encrypted." << std::endl;
|
|
@@ -895,12 +906,12 @@ int smudge (int argc, const char** argv)
|
|
std::clog << "git-crypt: this file may be unencrypted in the repository's history. If this" << std::endl;
|
|
std::clog << "git-crypt: file contains sensitive information, you can use 'git filter-branch'" << std::endl;
|
|
std::clog << "git-crypt: to remove its old versions from the history." << std::endl;
|
|
- std::cout.write(reinterpret_cast<char*>(header), std::cin.gcount()); // include the bytes which we already read
|
|
- std::cout << std::cin.rdbuf();
|
|
+ out.write(reinterpret_cast<char*>(header), in.gcount()); // include the bytes which we already read
|
|
+ out << in.rdbuf();
|
|
return 0;
|
|
}
|
|
|
|
- return decrypt_file_to_stdout(key_file, header, std::cin);
|
|
+ return decrypt_file_to_stream(key_file, header, in, out);
|
|
}
|
|
|
|
int diff (int argc, const char** argv)
|
|
@@ -942,7 +953,107 @@ int diff (int argc, const char** argv)
|
|
}
|
|
|
|
// Go ahead and decrypt it
|
|
- return decrypt_file_to_stdout(key_file, header, in);
|
|
+ return decrypt_file_to_stream(key_file, header, in);
|
|
+}
|
|
+
|
|
+int merge (int argc, const char** argv)
|
|
+{
|
|
+ const char* key_name = 0; // unused but needed
|
|
+ const char* key_path = 0; // unused but needed
|
|
+ const char* current_path = 0; // %A
|
|
+ const char* base_path = 0; // %O
|
|
+ const char* other_path = 0; // %B
|
|
+ const char* marker_size = 0; // %L
|
|
+
|
|
+ int argi = parse_plumbing_options(&key_name, &key_path, argc, argv);
|
|
+ if (argc - argi == 4) {
|
|
+ current_path = argv[argi];
|
|
+ base_path = argv[argi + 1];
|
|
+ other_path = argv[argi + 2];
|
|
+ marker_size = argv[argi + 3];
|
|
+ } else {
|
|
+ std::clog << "Usage: git-crypt merge [--key-name=NAME] [--key-file=PATH] CURRENT BASE OTHER MARKER_SIZE" << std::endl;
|
|
+ return 2;
|
|
+ }
|
|
+
|
|
+ // Run smudge on input files
|
|
+ std::vector<std::string> smudge_files;
|
|
+ smudge_files.push_back(current_path);
|
|
+ smudge_files.push_back(base_path);
|
|
+ smudge_files.push_back(other_path);
|
|
+
|
|
+ for (std::vector<std::string>::const_iterator file(smudge_files.begin()); file != smudge_files.end(); ++file) {
|
|
+ std::ifstream in(*file, std::ifstream::binary);
|
|
+ if (!in) {
|
|
+ std::clog << "git-crypt: " << *file << ": unable to open for reading" << std::endl;
|
|
+ return 1;
|
|
+ }
|
|
+ in.exceptions(std::ifstream::badbit);
|
|
+
|
|
+ std::ofstream out(*file + ".tmp", std::ofstream::binary | std::ofstream::trunc);
|
|
+ if (!out) {
|
|
+ std::clog << "git-crypt: " << *file << ".tmp: unable to open for writing" << std::endl;
|
|
+ return 1;
|
|
+ }
|
|
+ out.exceptions(std::ifstream::badbit);
|
|
+
|
|
+ if (smudge(argi, argv, in, out) != 0) {
|
|
+ std::clog << "Error: failed to smudge " << *file << ": unable to merge file" << std::endl;
|
|
+ return 1;
|
|
+ }
|
|
+ in.close();
|
|
+ out.close();
|
|
+ }
|
|
+
|
|
+ // git merge-file --marker-size <marker_size> <current_path> <base_path> <other_path>
|
|
+ std::vector<std::string> command;
|
|
+ command.push_back("git");
|
|
+ command.push_back("merge-file");
|
|
+ command.push_back("-L");
|
|
+ command.push_back("ours");
|
|
+ command.push_back("-L");
|
|
+ command.push_back("base");
|
|
+ command.push_back("-L");
|
|
+ command.push_back("theirs");
|
|
+ command.push_back("--marker-size");
|
|
+ command.push_back(marker_size);
|
|
+ command.push_back(std::string(current_path) + ".tmp");
|
|
+ command.push_back(std::string(base_path) + ".tmp");
|
|
+ command.push_back(std::string(other_path) + ".tmp");
|
|
+ int ret = exit_status(exec_command(command));
|
|
+
|
|
+ // Run clean on output file
|
|
+ // We have to clean (encrypt) the output file because git runs smudge filter on it
|
|
+ // afterwards which would complain about the file not being encrypted.
|
|
+ {
|
|
+ std::ifstream in(std::string(current_path) + ".tmp", std::ifstream::binary);
|
|
+ if (!in) {
|
|
+ std::clog << "git-crypt: " << current_path << ".tmp: unable to open for reading" << std::endl;
|
|
+ return 1;
|
|
+ }
|
|
+ in.exceptions(std::ifstream::badbit);
|
|
+
|
|
+ std::ofstream out(current_path, std::ofstream::binary | std::ofstream::trunc);
|
|
+ if (!out) {
|
|
+ std::clog << "git-crypt: " << current_path << ": unable to open for writing" << std::endl;
|
|
+ return 1;
|
|
+ }
|
|
+ out.exceptions(std::ifstream::badbit);
|
|
+
|
|
+ if (clean(argi, argv, in, out) != 0) {
|
|
+ std::clog << "Error: failed to clean " << current_path << ": unable to merge file" << std::endl;
|
|
+ return 1;
|
|
+ }
|
|
+ in.close();
|
|
+ out.close();
|
|
+ }
|
|
+
|
|
+ // Clean-up temporary files
|
|
+ for (std::vector<std::string>::const_iterator file(smudge_files.begin()); file != smudge_files.end(); ++file) {
|
|
+ remove_file(*file + ".tmp");
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
}
|
|
|
|
void help_init (std::ostream& out)
|
|
diff --git a/commands.hpp b/commands.hpp
|
|
index f441e93..51f4aea 100644
|
|
--- a/commands.hpp
|
|
+++ b/commands.hpp
|
|
@@ -33,6 +33,7 @@
|
|
|
|
#include <string>
|
|
#include <iosfwd>
|
|
+#include <iostream>
|
|
|
|
struct Error {
|
|
std::string message;
|
|
@@ -41,9 +42,10 @@ struct Error {
|
|
};
|
|
|
|
// Plumbing commands:
|
|
-int clean (int argc, const char** argv);
|
|
-int smudge (int argc, const char** argv);
|
|
+int clean (int argc, const char** argv, std::istream& in = std::cin, std::ostream& out = std::cout);
|
|
+int smudge (int argc, const char** argv, std::istream& in = std::cin, std::ostream& out = std::cout);
|
|
int diff (int argc, const char** argv);
|
|
+int merge (int argc, const char** argv);
|
|
// Public commands:
|
|
int init (int argc, const char** argv);
|
|
int unlock (int argc, const char** argv);
|
|
diff --git a/doc/multiple_keys.md b/doc/multiple_keys.md
|
|
index 6d7fc69..66b462a 100644
|
|
--- a/doc/multiple_keys.md
|
|
+++ b/doc/multiple_keys.md
|
|
@@ -11,7 +11,7 @@ option to `git-crypt init` as follows:
|
|
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
|
|
+ secretfile filter=git-crypt-KEYNAME diff=git-crypt-KEYNAME merge=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`
|
|
diff --git a/git-crypt.cpp b/git-crypt.cpp
|
|
index 9505834..d8c2072 100644
|
|
--- a/git-crypt.cpp
|
|
+++ b/git-crypt.cpp
|
|
@@ -73,6 +73,7 @@ static void print_usage (std::ostream& out)
|
|
out << " clean [LEGACY-KEYFILE]" << std::endl;
|
|
out << " smudge [LEGACY-KEYFILE]" << std::endl;
|
|
out << " diff [LEGACY-KEYFILE] FILE" << std::endl;
|
|
+ out << " merge" << std::endl;
|
|
*/
|
|
out << std::endl;
|
|
out << "See 'git-crypt help COMMAND' for more information on a specific command." << std::endl;
|
|
@@ -231,6 +232,9 @@ try {
|
|
if (std::strcmp(command, "diff") == 0) {
|
|
return diff(argc, argv);
|
|
}
|
|
+ if (std::strcmp(command, "merge") == 0) {
|
|
+ return merge(argc, argv);
|
|
+ }
|
|
} catch (const Option_error& e) {
|
|
std::clog << "git-crypt: Error: " << e.option_name << ": " << e.message << std::endl;
|
|
help_for_command(command, std::clog);
|
|
diff --git a/man/git-crypt.xml b/man/git-crypt.xml
|
|
index f8ec765..4b2e631 100644
|
|
--- a/man/git-crypt.xml
|
|
+++ b/man/git-crypt.xml
|
|
@@ -310,11 +310,11 @@
|
|
<para>
|
|
Then, you specify the files to encrypt by creating a
|
|
<citerefentry><refentrytitle>gitattributes</refentrytitle><manvolnum>5</manvolnum></citerefentry> file.
|
|
- Each file which you want to encrypt should be assigned the "<literal>filter=git-crypt diff=git-crypt</literal>"
|
|
+ Each file which you want to encrypt should be assigned the "<literal>filter=git-crypt diff=git-crypt merge=git-crypt</literal>"
|
|
attributes. For example:
|
|
</para>
|
|
|
|
- <screen>secretfile filter=git-crypt diff=git-crypt *.key filter=git-crypt diff=git-crypt</screen>
|
|
+ <screen>secretfile filter=git-crypt diff=git-crypt merge=git-crypt *.key filter=git-crypt diff=git-crypt merge=git-crypt</screen>
|
|
|
|
<para>
|
|
Like a <filename>.gitignore</filename> file, <filename>.gitattributes</filename> files can match wildcards and
|
|
@@ -383,7 +383,7 @@
|
|
following in <filename>dir/.gitattributes</filename>:
|
|
</para>
|
|
|
|
- <screen>* filter=git-crypt diff=git-crypt .gitattributes !filter !diff</screen>
|
|
+ <screen>* filter=git-crypt diff=git-crypt merge=git-crypt .gitattributes !filter !diff !merge</screen>
|
|
|
|
<para>
|
|
The second pattern is essential for ensuring that <filename>.gitattributes</filename> itself
|
|
@@ -414,7 +414,7 @@
|
|
filter in <filename>.gitattributes</filename> as follows:
|
|
</para>
|
|
|
|
- <screen><replaceable>secretfile</replaceable> filter=git-crypt-<replaceable>KEYNAME</replaceable> diff=git-crypt-<replaceable>KEYNAME</replaceable></screen>
|
|
+ <screen><replaceable>secretfile</replaceable> filter=git-crypt-<replaceable>KEYNAME</replaceable> diff=git-crypt-<replaceable>KEYNAME</replaceable> merge=git-crypt-<replaceable>KEYNAME</replaceable></screen>
|
|
|
|
<para>
|
|
To export an alternative key or share it with a GPG user, pass the
|