From da5a8e4feff6173307393c85b82a6329a84b41cd Mon Sep 17 00:00:00 2001 From: Sam Lin Date: Sun, 10 Nov 2024 02:47:42 -0600 Subject: [PATCH 01/10] Update OpenSSL includes and compatibility checks in crypto files https://github.com/AGWA/git-crypt/pull/249 --- crypto-openssl-10.cpp | 4 ++-- crypto-openssl-11.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crypto-openssl-10.cpp b/crypto-openssl-10.cpp index f0f2c53..93079ef 100644 --- a/crypto-openssl-10.cpp +++ b/crypto-openssl-10.cpp @@ -28,9 +28,9 @@ * as that of the covered work. */ -#include +#include -#if !defined(OPENSSL_API_COMPAT) +#if defined(HMAC_cleanup) #include "crypto.hpp" #include "key.hpp" diff --git a/crypto-openssl-11.cpp b/crypto-openssl-11.cpp index adf03bb..5fe23ef 100644 --- a/crypto-openssl-11.cpp +++ b/crypto-openssl-11.cpp @@ -28,9 +28,9 @@ * as that of the covered work. */ -#include +#include -#if defined(OPENSSL_API_COMPAT) +#if !defined(HMAC_cleanup) #include "crypto.hpp" #include "key.hpp" From 7e183d676601cdaa3298f77b168b7fbf8c786666 Mon Sep 17 00:00:00 2001 From: maxisam <456807+maxisam@users.noreply.github.com.> Date: Mon, 11 Nov 2024 13:07:24 -0600 Subject: [PATCH 02/10] chore: Update GitHub Actions to use latest checkout and script versions --- .github/workflows/release-linux.yml | 4 ++-- .github/workflows/release-windows.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index ea98525..ee5e44a 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -10,7 +10,7 @@ jobs: contents: read steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Install dependencies run: sudo apt install libssl-dev - name: Build binary @@ -32,7 +32,7 @@ jobs: with: name: git-crypt-artifacts - name: Upload release asset - uses: actions/github-script@v3 + uses: actions/github-script@v6 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/release-windows.yml b/.github/workflows/release-windows.yml index e82e992..c8c19c3 100644 --- a/.github/workflows/release-windows.yml +++ b/.github/workflows/release-windows.yml @@ -10,7 +10,7 @@ jobs: contents: read steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup msys2 uses: msys2/setup-msys2@v2 with: @@ -42,7 +42,7 @@ jobs: with: name: git-crypt-artifacts - name: Upload release asset - uses: actions/github-script@v3 + uses: actions/github-script@v6 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | From 7351e2afb3fdeb60a7527a5c2521e2d649869698 Mon Sep 17 00:00:00 2001 From: maxisam <456807+maxisam@users.noreply.github.com.> Date: Mon, 11 Nov 2024 13:38:52 -0600 Subject: [PATCH 03/10] feat: Add support for push events to create dev build --- .github/workflows/release-linux.yml | 19 +++++++++++++++++-- .github/workflows/release-windows.yml | 18 +++++++++++++++++- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index ee5e44a..9438dff 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -1,6 +1,9 @@ on: release: types: [published] + push: + branches: + - master name: Build Release Binary (Linux) jobs: build: @@ -15,20 +18,31 @@ jobs: run: sudo apt install libssl-dev - name: Build binary run: make + - name: artifacts name + id: artifacts-name + env: + IS_RELEASE: ${{ github.event_name == 'release' }} + run: | + if [ "$IS_RELEASE" = true ]; then + echo "artifacts-name=git-crypt-artifacts" >> $GITHUB_ENV + else + echo "artifacts-name=git-crypt-artifacts-dev" >> $GITHUB_ENV + fi - name: Upload release artifact uses: actions/upload-artifact@v4 with: - name: git-crypt-artifacts + name: ${{ env.artifacts-name }} path: git-crypt upload: name: Upload Release Binary + if: github.event_name == 'release' runs-on: ubuntu-latest needs: build permissions: contents: write steps: - name: Download release artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v2 with: name: git-crypt-artifacts - name: Upload release asset @@ -44,3 +58,4 @@ jobs: name: 'git-crypt-${{ github.event.release.name }}-linux-x86_64', data: await fs.readFile('git-crypt'), }); + \ No newline at end of file diff --git a/.github/workflows/release-windows.yml b/.github/workflows/release-windows.yml index c8c19c3..2fb6b0f 100644 --- a/.github/workflows/release-windows.yml +++ b/.github/workflows/release-windows.yml @@ -1,6 +1,9 @@ on: release: types: [published] + push: + branches: + - master name: Build Release Binary (Windows) jobs: build: @@ -25,14 +28,26 @@ jobs: - name: Build binary shell: msys2 {0} run: make LDFLAGS="-static-libstdc++ -static -lcrypto -lws2_32" + - name: artifacts name + id: artifacts-name + env: + IS_RELEASE: ${{ github.event_name == 'release' }} + run: | + $artifactsName = if ($env:IS_RELEASE -eq "true") { + "git-crypt-artifacts" + } else { + "git-crypt-artifacts-dev" + } + "artifacts-name=$artifactsName" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - name: Upload release artifact uses: actions/upload-artifact@v4 with: - name: git-crypt-artifacts + name: ${{ env.artifacts-name }} path: git-crypt.exe upload: name: Upload Release Binary runs-on: ubuntu-latest + if: github.event_name == 'release' needs: build permissions: contents: write @@ -54,3 +69,4 @@ jobs: name: 'git-crypt-${{ github.event.release.name }}-x86_64.exe', data: await fs.readFile('git-crypt.exe'), }); + \ No newline at end of file From b9e8be50cf4786a5c195112e344ec45c027d1187 Mon Sep 17 00:00:00 2001 From: maxisam <456807+maxisam@users.noreply.github.com.> Date: Mon, 11 Nov 2024 13:01:26 -0600 Subject: [PATCH 04/10] feat: Enhance git_config functions to support worktree option #105 --- commands.cpp | 14 +++++++++----- commands.hpp | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/commands.cpp b/commands.cpp index 6b3c498..11005d8 100644 --- a/commands.cpp +++ b/commands.cpp @@ -111,11 +111,12 @@ static std::vector make_version (int a, int b, int c) return version; } -static void git_config (const std::string& name, const std::string& value) +static void git_config (const std::string& name, const std::string& value, bool from_worktree=true) { std::vector command; command.push_back("git"); command.push_back("config"); + if (from_worktree) command.push_back("--worktree"); command.push_back(name); command.push_back(value); @@ -124,11 +125,12 @@ static void git_config (const std::string& name, const std::string& value) } } -static bool git_has_config (const std::string& name) +static bool git_has_config (const std::string& name, bool from_worktree=true) { std::vector command; command.push_back("git"); command.push_back("config"); + if (from_worktree) command.push_back("--worktree"); command.push_back("--get-all"); command.push_back(name); @@ -140,11 +142,12 @@ static bool git_has_config (const std::string& name) } } -static void git_deconfig (const std::string& name) +static void git_deconfig (const std::string& name, bool from_worktree=true) { std::vector command; command.push_back("git"); command.push_back("config"); + if (from_worktree) command.push_back("--worktree"); command.push_back("--remove-section"); command.push_back(name); @@ -277,12 +280,13 @@ static std::string get_internal_key_path (const char* key_name) return path; } -std::string get_git_config (const std::string& name) +std::string get_git_config (const std::string& name, bool from_worktree) { // git config --get std::vector command; command.push_back("git"); command.push_back("config"); + if (from_worktree) command.push_back("--worktree"); command.push_back("--get"); command.push_back(name); @@ -321,7 +325,7 @@ static std::string get_repo_state_path () } // Check if the repo state dir has been explicitly configured. If so, use that in path construction. - if (git_has_config("git-crypt.repoStateDir")) { + if (git_has_config("git-crypt.repoStateDir", false)) { std::string repoStateDir = get_git_config("git-crypt.repoStateDir"); // The repoStateDir value must always be relative to git work tree to ensure the repoStateDir can be committed diff --git a/commands.hpp b/commands.hpp index f441e93..7001f32 100644 --- a/commands.hpp +++ b/commands.hpp @@ -71,6 +71,6 @@ void help_refresh (std::ostream&); void help_status (std::ostream&); // other -std::string get_git_config (const std::string& name); +std::string get_git_config (const std::string& name, bool from_worktree=true); #endif From 5e6b3f901dcc701f831f6adf41df8292712df5e8 Mon Sep 17 00:00:00 2001 From: maxisam <456807+maxisam@users.noreply.github.com.> Date: Mon, 11 Nov 2024 14:22:36 -0600 Subject: [PATCH 05/10] fix: Update Windows release workflow to hand open ssl issue --- .github/workflows/release-windows.yml | 6 +++++- Makefile | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-windows.yml b/.github/workflows/release-windows.yml index 2fb6b0f..adac94a 100644 --- a/.github/workflows/release-windows.yml +++ b/.github/workflows/release-windows.yml @@ -25,9 +25,13 @@ jobs: mingw-w64-x86_64-toolchain mingw-w64-x86_64-openssl openssl-devel + - name: Build binary shell: msys2 {0} - run: make LDFLAGS="-static-libstdc++ -static -lcrypto -lws2_32" + run: | + make clean + make ENABLE_MAN=no + - name: artifacts name id: artifacts-name env: diff --git a/Makefile b/Makefile index 68eb9db..dea18d0 100644 --- a/Makefile +++ b/Makefile @@ -4,15 +4,22 @@ # See COPYING file for license information. # +# Compiler Flags CXXFLAGS ?= -Wall -pedantic -Wno-long-long -O2 CXXFLAGS += -std=c++11 +# Added -Wno-deprecated-declarations to suppress deprecated OpenSSL warnings +CXXFLAGS += -Wno-deprecated-declarations + +# Installation Directories PREFIX ?= /usr/local BINDIR ?= $(PREFIX)/bin MANDIR ?= $(PREFIX)/share/man +# Documentation ENABLE_MAN ?= no DOCBOOK_XSL ?= http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl +# Object Files OBJFILES = \ git-crypt.o \ commands.o \ @@ -25,14 +32,31 @@ OBJFILES = \ fhstream.o OBJFILES += crypto-openssl-10.o crypto-openssl-11.o + +# Linker Flags +# Initially includes -lcrypto; additional flags will be appended based on OS LDFLAGS += -lcrypto +# Detect Operating System +# $(OS) is typically 'Windows_NT' on Windows and empty or 'Linux' on Linux +ifeq ($(OS),Windows_NT) + # Windows-specific linker flags + LDFLAGS += -lws2_32 -lcrypt32 +else + # Unix/Linux-specific linker flags + # You can add Unix-specific flags here if needed + # For example, linking against pthread: + # LDFLAGS += -lpthread +endif + +# Documentation Tools XSLTPROC ?= xsltproc DOCBOOK_FLAGS += --param man.output.in.separate.dir 1 \ --stringparam man.output.base.dir man/ \ --param man.output.subdirs.enabled 1 \ --param man.authors.section.enabled 1 +# Targets all: build # @@ -46,12 +70,15 @@ build: $(BUILD_TARGETS) build-bin: git-crypt +# Linking the binary git-crypt: $(OBJFILES) $(CXX) $(CXXFLAGS) -o $@ $(OBJFILES) $(LDFLAGS) +# Object File Dependencies util.o: util.cpp util-unix.cpp util-win32.cpp coprocess.o: coprocess.cpp coprocess-unix.cpp coprocess-win32.cpp +# Building Manual Pages build-man: man/man1/git-crypt.1 man/man1/git-crypt.1: man/git-crypt.xml From 23b053cb5a53204302ab37d349fba298f5ff5865 Mon Sep 17 00:00:00 2001 From: maxisam <456807+maxisam@users.noreply.github.com.> Date: Mon, 11 Nov 2024 17:11:23 -0600 Subject: [PATCH 06/10] fix: Update release workflows to use the new GitHub REST API for uploading release assets --- .github/workflows/release-linux.yml | 2 +- .github/workflows/release-windows.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index 9438dff..239de54 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -52,7 +52,7 @@ jobs: script: | const fs = require("fs").promises; const { repo: { owner, repo }, sha } = context; - await github.repos.uploadReleaseAsset({ + await github.rest.repos.uploadReleaseAsset({ owner, repo, release_id: ${{ github.event.release.id }}, name: 'git-crypt-${{ github.event.release.name }}-linux-x86_64', diff --git a/.github/workflows/release-windows.yml b/.github/workflows/release-windows.yml index adac94a..9f20e72 100644 --- a/.github/workflows/release-windows.yml +++ b/.github/workflows/release-windows.yml @@ -67,7 +67,7 @@ jobs: script: | const fs = require("fs").promises; const { repo: { owner, repo }, sha } = context; - await github.repos.uploadReleaseAsset({ + await github.rest.repos.uploadReleaseAsset({ owner, repo, release_id: ${{ github.event.release.id }}, name: 'git-crypt-${{ github.event.release.name }}-x86_64.exe', From 622af2523c730e0a4fabf984475d7cf28e0511d2 Mon Sep 17 00:00:00 2001 From: maxisam <456807+maxisam@users.noreply.github.com.> Date: Mon, 11 Nov 2024 17:18:04 -0600 Subject: [PATCH 07/10] fix: add worktree while unlock https://github.com/AGWA/git-crypt/pull/222 --- commands.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/commands.cpp b/commands.cpp index 11005d8..6ece35b 100644 --- a/commands.cpp +++ b/commands.cpp @@ -246,12 +246,13 @@ static std::string get_internal_state_path () std::vector command; command.push_back("git"); command.push_back("rev-parse"); - command.push_back("--git-dir"); + command.push_back("--git-path"); + command.push_back("common/git-crypt"); std::stringstream output; if (!successful_exit(exec_command(command, output))) { - throw Error("'git rev-parse --git-dir' failed - is this a Git repository?"); + throw Error("'git rev-parse --git-path common/git-crypt' failed - is this a Git repository?"); } std::string path; From e1175e3514f1274f45d47cce3173993eef1a001b Mon Sep 17 00:00:00 2001 From: maxisam <456807+maxisam@users.noreply.github.com.> Date: Mon, 11 Nov 2024 23:20:12 -0600 Subject: [PATCH 08/10] feat: change push event to pull_request for Linux and Windows release workflows --- .github/workflows/release-linux.yml | 2 +- .github/workflows/release-windows.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index 239de54..e809afb 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -1,7 +1,7 @@ on: release: types: [published] - push: + pull_request: branches: - master name: Build Release Binary (Linux) diff --git a/.github/workflows/release-windows.yml b/.github/workflows/release-windows.yml index 9f20e72..3e5b14c 100644 --- a/.github/workflows/release-windows.yml +++ b/.github/workflows/release-windows.yml @@ -1,7 +1,7 @@ on: release: types: [published] - push: + pull_request: branches: - master name: Build Release Binary (Windows) From 7b80be52a6a2a340c97f0752ca5ec0e31c087c78 Mon Sep 17 00:00:00 2001 From: maxisam <456807+maxisam@users.noreply.github.com.> Date: Mon, 11 Nov 2024 23:21:58 -0600 Subject: [PATCH 09/10] chore: add .vscode directory to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 81afb4a..584b8c2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ *.o git-crypt + +.vscode/* \ No newline at end of file From 8a33586185abc1d2e078fef803429e5d19403ce2 Mon Sep 17 00:00:00 2001 From: maxisam <456807+maxisam@users.noreply.github.com.> Date: Mon, 11 Nov 2024 23:58:18 -0600 Subject: [PATCH 10/10] feat: add merge support for git-crypt and update documentation https://github.com/AGWA/git-crypt/pull/180 --- README | 9 ++- README.md | 9 ++- commands.cpp | 149 +++++++++++++++++++++++++++++++++++++------ commands.hpp | 6 +- doc/multiple_keys.md | 2 +- git-crypt.cpp | 4 ++ man/git-crypt.xml | 12 ++-- 7 files changed, 153 insertions(+), 38 deletions(-) diff --git a/README b/README index f4c0ec3..006195f 100644 --- a/README +++ b/README @@ -28,9 +28,8 @@ Configure a repository to use git-crypt: Specify files to encrypt by creating a .gitattributes file: - secretfile filter=git-crypt diff=git-crypt - *.key filter=git-crypt diff=git-crypt - secretdir/** filter=git-crypt diff=git-crypt + secretfile filter=git-crypt diff=git-crypt merge=git-crypt + *.key filter=git-crypt diff=git-crypt merge=git-crypt Like a .gitignore file, it can match wildcards and should be checked into the repository. See below for more information about .gitattributes. @@ -151,10 +150,10 @@ encrypt all files beneath it. Also note that the pattern `dir/*` does not match files under sub-directories of dir/. To encrypt an entire sub-tree dir/, use `dir/**`: - dir/** filter=git-crypt diff=git-crypt + dir/** filter=git-crypt diff=git-crypt merge=git-crypt The .gitattributes file must not be encrypted, so make sure wildcards don't match it accidentally. If necessary, you can exclude .gitattributes from encryption like this: - .gitattributes !filter !diff + .gitattributes !filter !diff !merge diff --git a/README.md b/README.md index 945735c..d3acc92 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,8 @@ Configure a repository to use git-crypt: Specify files to encrypt by creating a .gitattributes file: - secretfile filter=git-crypt diff=git-crypt - *.key filter=git-crypt diff=git-crypt - secretdir/** filter=git-crypt diff=git-crypt + secretfile filter=git-crypt diff=git-crypt merge=git-crypt + *.key filter=git-crypt diff=git-crypt merge=git-crypt Like a .gitignore file, it can match wildcards and should be checked into the repository. See below for more information about .gitattributes. @@ -153,10 +152,10 @@ encrypt all files beneath it. Also note that the pattern `dir/*` does not match files under sub-directories of dir/. To encrypt an entire sub-tree dir/, use `dir/**`: - dir/** filter=git-crypt diff=git-crypt + dir/** filter=git-crypt diff=git-crypt merge=git-crypt The .gitattributes file must not be encrypted, so make sure wildcards don't match it accidentally. If necessary, you can exclude .gitattributes from encryption like this: - .gitattributes !filter !diff + .gitattributes !filter !diff !merge diff --git a/commands.cpp b/commands.cpp index 6ece35b..b3a052a 100644 --- a/commands.cpp +++ b/commands.cpp @@ -169,11 +169,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"); } } @@ -190,6 +195,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::const_iterator paths_begin, std::vector::const_iterator paths_end) @@ -717,8 +728,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; @@ -751,10 +762,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(buffer), bytes_read); file_size += bytes_read; @@ -802,8 +813,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(digest), Aes_ctr_encryptor::NONCE_LEN); // ...includes the nonce + out.write("\0GITCRYPT\0", 10); // ...identifies this as an encrypted file + out.write(reinterpret_cast(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); @@ -814,7 +825,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(buffer), buffer_len); - std::cout.write(buffer, buffer_len); + out.write(buffer, buffer_len); file_data += buffer_len; file_data_len -= buffer_len; } @@ -830,14 +841,14 @@ int clean (int argc, const char** argv) aes.process(reinterpret_cast(buffer), reinterpret_cast(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 @@ -855,7 +866,7 @@ static int decrypt_file_to_stdout (const Key_file& key_file, const unsigned char in.read(reinterpret_cast(buffer), sizeof(buffer)); aes.process(buffer, buffer, in.gcount()); hmac.add(buffer, in.gcount()); - std::cout.write(reinterpret_cast(buffer), in.gcount()); + out.write(reinterpret_cast(buffer), in.gcount()); } unsigned char digest[Hmac_sha1_state::LEN]; @@ -871,8 +882,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; @@ -891,8 +902,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(header), sizeof(header)); - if (std::cin.gcount() != sizeof(header) || std::memcmp(header, "\0GITCRYPT\0", 10) != 0) { + in.read(reinterpret_cast(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; @@ -900,12 +911,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(header), std::cin.gcount()); // include the bytes which we already read - std::cout << std::cin.rdbuf(); + out.write(reinterpret_cast(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) @@ -947,7 +958,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 smudge_files; + smudge_files.push_back(current_path); + smudge_files.push_back(base_path); + smudge_files.push_back(other_path); + + for (std::vector::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 + std::vector 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::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 7001f32..3895e0d 100644 --- a/commands.hpp +++ b/commands.hpp @@ -33,6 +33,7 @@ #include #include +#include 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..a1c0cd0 100644 --- a/man/git-crypt.xml +++ b/man/git-crypt.xml @@ -7,8 +7,8 @@ --> git-crypt - 2022-04-21 - git-crypt 0.7.0 + 2024-11-11 + git-crypt 0.8.0 Andrew Ayer @@ -310,11 +310,11 @@ Then, you specify the files to encrypt by creating a gitattributes5 file. - Each file which you want to encrypt should be assigned the "filter=git-crypt diff=git-crypt" + Each file which you want to encrypt should be assigned the "filter=git-crypt diff=git-crypt merge=git-crypt" attributes. For example: - secretfile filter=git-crypt diff=git-crypt *.key filter=git-crypt diff=git-crypt + secretfile filter=git-crypt diff=git-crypt merge=git-crypt *.key filter=git-crypt diff=git-crypt merge=git-crypt Like a .gitignore file, .gitattributes files can match wildcards and @@ -383,7 +383,7 @@ following in dir/.gitattributes: - * filter=git-crypt diff=git-crypt .gitattributes !filter !diff + * filter=git-crypt diff=git-crypt merge=git-crypt .gitattributes !filter !diff !merge The second pattern is essential for ensuring that .gitattributes itself @@ -414,7 +414,7 @@ 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