1
0
Fork 0
mirror of https://gitlab.alpinelinux.org/alpine/aports.git synced 2025-07-21 18:25:41 +03:00
aports/community/git-crypt/0001-add-merge-driver.patch
2022-09-14 22:08:28 +00:00

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&#10;*.key filter=git-crypt diff=git-crypt</screen>
+ <screen>secretfile filter=git-crypt diff=git-crypt merge=git-crypt&#10;*.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&#10;.gitattributes !filter !diff</screen>
+ <screen>* filter=git-crypt diff=git-crypt merge=git-crypt&#10;.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