mirror of
				https://gitlab.alpinelinux.org/alpine/aports.git
				synced 2025-10-31 00:12:05 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			366 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			366 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
 |