From 0effec1282a97cec59210672da726f598c9a9092 Mon Sep 17 00:00:00 2001 From: Andrew Thorburn Date: Thu, 19 Dec 2019 21:17:03 +0100 Subject: [PATCH] Support including authors as Co-authored-by Add a config option, git-together.co-authored, which will switch git-together from using Signed-off-by to Co-authored-by. This makes it easier to support mobbing, as all authors are included in the commit message, and this is supported by GitHub (c.f. https://help.github.com/en/github/committing-changes-to-your-project/creating-a-commit-with-multiple-authors ). This is mutually exlusive with signing off commits - you have to choose one or the other. This is mostly because it doesn't make sense to include a single extra committer as the one who signed it off, but it also means that, in the event that a commit _must_ be signed off, e.g., when committing to the Linux Kernel, you don't lose information. This is a little fragile, as we are parsing multiple git options, and potentially reading from stdin, as there are at least six different ways to create a commit message in Git, four of which we care about, and two of which we should at least acknowledge, even though we don't modify them in any way. This also fixes #20 by excluding --signoff from the list of args when using --amend. --- .gitignore | 1 + Cargo.lock | 123 ++++++++++++++++++++++++++++++++--- Cargo.toml | 1 + README.md | 15 +++++ src/errors.rs | 7 +- src/lib.rs | 175 +++++++++++++++++++++++++++++++++++++++++++++++--- 6 files changed, 301 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index eb5a316..c507849 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ target +.idea diff --git a/Cargo.lock b/Cargo.lock index c910afa..ee0adb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,7 +12,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -22,7 +22,7 @@ version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -30,6 +30,14 @@ name = "bitflags" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "c2-chacha" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "cc" version = "1.0.40" @@ -46,7 +54,7 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", "openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", @@ -62,12 +70,23 @@ dependencies = [ "backtrace 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "getrandom" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "git-together" version = "0.1.0-alpha.18" dependencies = [ "error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "git2 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -76,7 +95,7 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "libgit2-sys 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -96,7 +115,7 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.62" +version = "0.2.66" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -106,7 +125,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", "curl-sys 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "libssh2-sys 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", "openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)", @@ -119,7 +138,7 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", "openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", @@ -132,7 +151,7 @@ version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", "vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -162,7 +181,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", "vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -177,6 +196,61 @@ name = "pkg-config" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ppv-lite86" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_chacha" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "remove_dir_all" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rustc-demangle" version = "0.1.16" @@ -187,6 +261,19 @@ name = "smallvec" version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "tempfile" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "unicode-bidi" version = "0.3.4" @@ -218,6 +305,11 @@ name = "vcpkg" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "wasi" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "winapi" version = "0.3.7" @@ -242,13 +334,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum backtrace 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)" = "1371048253fa3bac6704bfd6bbfc922ee9bdcee8881330d40f308b81cc5adc55" "checksum backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b" "checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" +"checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" "checksum cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "b548a4ee81fccb95919d4e22cfea83c7693ebfd78f0495493178db20b3139da7" "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" "checksum curl-sys 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)" = "5e90ae10f635645cba9cad1023535f54915a95c58c44751c6ed70dbaeb17a408" "checksum error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9435d864e017c3c6afeac1654189b06cdb491cf2ff73dbf0d73b0f292f42ff8" +"checksum getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407" "checksum git2 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "591f8be1674b421644b6c030969520bc3fa12114d2eb467471982ed3e9584e71" "checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" -"checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" +"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" "checksum libgit2-sys 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)" = "48441cb35dc255da8ae72825689a95368bf510659ae1ad55dc4aa88cb1789bf1" "checksum libssh2-sys 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "126a1f4078368b163bfdee65fbab072af08a1b374a5551b21e87ade27b1fbf9d" "checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe" @@ -258,12 +352,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)" = "f4fad9e54bd23bd4cbbe48fdc08a1b8091707ac869ef8508edea2fec77dcc884" "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" "checksum pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c1d2cfa5a714db3b5f24f0915e74fcdf91d09d496ba61329705dda7774d2af" +"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" +"checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412" +"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" +"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" "checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" +"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" "checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" "checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" "checksum vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "33dd455d0f96e90a75803cfeb7f948768c08d70a6de9a8d2362461935698bf95" +"checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" "checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 9becaf2..0a6a769 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,4 @@ edition = "2018" [dependencies] error-chain = "0.10" git2 = "0.7" +tempfile = "3.1.0" \ No newline at end of file diff --git a/README.md b/README.md index ddb223f..df66cbe 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,7 @@ Soloing and mobbing are automatically set by the number of authors passed to that the author/committer roles are fairly spread across the pair/mob over time. +### Aliases Aliases are supported as well. You can make git-together do its thing when you use an alias for a committing command by configuring a comma-separated list of aliases: @@ -75,6 +76,20 @@ git config git-together.aliases ci,rv,m git ci ``` +### Co-Author support +If you prefer to use the `Co-authored-by` flag [created by GitHub](https://help.github.com/en/github/committing-changes-to-your-project/creating-a-commit-with-multiple-authors), +there is an experimental command to enable that: + +```bash +git config git-together.co-authored 1 +``` + +This will add `Co-authored-by` lines for all authors (except the primary) to your +commit messages. It is _strongly_ recommended that you do not mix different message +styles, e.g., using `-F- -m 'foo' -F /foo/bar` as this will have unpredictable +results. + +### Author rotation By default, `git-together` sets and rotates pairs for a single local repository. If you are working across multiple repos with a pair on a regular basis, this can be difficult to set across all of them. The `--global` flag can diff --git a/src/errors.rs b/src/errors.rs index 4420a5c..0df0506 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1 +1,6 @@ -error_chain! {} +error_chain! { + foreign_links { + Fmt(::std::fmt::Error); + Io(::std::io::Error); + } +} diff --git a/src/lib.rs b/src/lib.rs index 081a6fc..c2c9449 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,25 +1,45 @@ -#![feature(slice_patterns)] #![recursion_limit = "1024"] #[macro_use] extern crate error_chain; extern crate git2; -pub mod author; -pub mod config; -pub mod errors; -pub mod git; - +use std::cell::RefCell; use std::collections::HashMap; use std::env; +use std::fs::File; +use std::io::{BufRead, BufReader, Write}; +use std::path::Path; use std::process::Command; +use tempfile::NamedTempFile; + use author::{Author, AuthorParser}; use config::Config; use errors::*; +pub mod author; +pub mod config; +pub mod errors; +pub mod git; + const NAMESPACE: &str = "git-together"; const TRIGGERS: [&str; 2] = ["with", "together"]; +const GIT_FILE_OPT_SHORT: &str = "-F"; +const GIT_STDIN_OPT_SHORT: &str = "-F-"; +const GIT_FILE_OPT_LONG: &str = "--file"; + +const GIT_FILE_OPT_READ_FROM_STDIN: &str = "-"; + +const GIT_REUSE_OPT_SHORT: &str = "-C"; +const GIT_REUSE_OPT_LONG: &str = "--reuse-message"; + +const GIT_REEDIT_OPT_SHORT: &str = "-c"; +const GIT_REEDIT_OPT_LONG: &str = "--reedit-message"; + +const GIT_MESSAGE_OPT_SHORT: &str = "-m"; +const GIT_MESSAGE_OPT_LONG: &str = "--message"; + fn namespaced(name: &str) -> String { format!("{}.{}", NAMESPACE, name) } @@ -102,14 +122,26 @@ pub fn run() -> Result { 0 } else if gt.is_signoff_cmd(command) { - if command == &"merge" { + if command == &"merge" || command_args.contains(&"--amend") { env::set_var("GIT_TOGETHER_NO_SIGNOFF", "1"); } let mut cmd = Command::new("git"); let cmd = cmd.args(global_args); let cmd = cmd.arg(command); - let cmd = gt.signoff(cmd)?; + + let co_authored = gt + .config + .get(&namespaced("co-authored")) + .unwrap_or_else(|_| "0".to_string()); + + let mut command_args: Vec = command_args.iter().map(|s| (*s).to_string()).collect(); + let cmd = if &co_authored == "0" { + gt.signoff(cmd)? + } else { + gt.authored_by(&mut command_args)?; + cmd + }; let cmd = cmd.args(command_args); let status = cmd.status().chain_err(|| "failed to execute process")?; @@ -128,9 +160,19 @@ pub fn run() -> Result { Ok(code) } +pub enum CommitMessageInputMethod { + File(String), + Message, + ReuseCommit, + ReuseCommitAndEdit, + Stdin, + Editor, +} + pub struct GitTogether { config: C, author_parser: AuthorParser, + temp_file: RefCell, } pub enum ConfigScope { @@ -159,6 +201,7 @@ impl GitTogether { Ok(GitTogether { config, author_parser, + temp_file: RefCell::new(NamedTempFile::new()?), }) } } @@ -263,6 +306,109 @@ impl GitTogether { }) } + pub fn authored_by(&self, command_args: &mut Vec) -> Result<()> { + let no_signoff = env::var("GIT_TOGETHER_NO_SIGNOFF").is_ok(); + let active = self.config.get(&namespaced("active"))?; + let initials: Vec<_> = active.split('+').collect(); + let authors = self.get_authors(&initials)?; + + if no_signoff || authors.len() <= 1 { + return Ok(()); + } + + let commit_message_input_method = + command_args + .iter() + .enumerate() + .find_map(|(idx, elem)| match elem.as_str() { + GIT_FILE_OPT_SHORT | GIT_FILE_OPT_LONG => { + match command_args[idx + 1].as_str() { + GIT_FILE_OPT_READ_FROM_STDIN => Some(CommitMessageInputMethod::Stdin), + v => Some(CommitMessageInputMethod::File(v.to_string())), + } + } + GIT_STDIN_OPT_SHORT => { + Some(CommitMessageInputMethod::Stdin) + } + GIT_REUSE_OPT_SHORT | GIT_REUSE_OPT_LONG => { + Some(CommitMessageInputMethod::ReuseCommit) + } + GIT_REEDIT_OPT_SHORT | GIT_REEDIT_OPT_LONG => { + Some(CommitMessageInputMethod::ReuseCommitAndEdit) + } + GIT_MESSAGE_OPT_SHORT | GIT_MESSAGE_OPT_LONG => { + Some(CommitMessageInputMethod::Message) + } + _ => None, + }); + + let commit_message_input_method = + commit_message_input_method.unwrap_or(CommitMessageInputMethod::Editor); + + let authored_by: Vec = authors + .iter() + .map(|a| format!("Co-authored-by: {} <{}>", a.name, a.email)) + .skip(1) + .collect(); + let authored_by_str = authored_by.join("\n"); + let temp_file_path = self.temp_file.borrow().path().to_str().unwrap().to_string(); + let find_first_idx = |list: &[String], match_against: &[&str]| -> usize { + list.iter() + .enumerate() + .find(|(_, elem)| match_against.contains(&elem.as_str())) + .unwrap_or((0, &"".to_string())) + .0 + }; + match commit_message_input_method { + CommitMessageInputMethod::Message => { + command_args.push("-m".to_string()); + command_args.push(authored_by_str); + } + CommitMessageInputMethod::Editor => { + self.temp_file + .borrow_mut() + .write_all(("\n\n".to_owned() + &authored_by_str).as_bytes())?; + command_args.push("-t".to_string()); + command_args.push(temp_file_path); + } + CommitMessageInputMethod::ReuseCommit => { /* Ignore - re-use without change */ } + CommitMessageInputMethod::ReuseCommitAndEdit => { + /* Ignore - hard to change and no guarantee the user wants this added */ + } + CommitMessageInputMethod::Stdin => { + let stdin_reader = BufReader::new(std::io::stdin()); + let mut lines: Vec = stdin_reader.lines().map(|l| l.unwrap()).collect(); + lines.push("".to_string()); + authored_by.iter().for_each(|i| lines.push(i.clone())); + self.temp_file + .borrow_mut() + .write_all(lines.join("\n").as_bytes())?; + let insert_idx = + find_first_idx(command_args, &[GIT_FILE_OPT_SHORT, GIT_FILE_OPT_LONG, GIT_STDIN_OPT_SHORT]); + if &command_args[insert_idx] == &GIT_STDIN_OPT_SHORT { + command_args[insert_idx] = GIT_FILE_OPT_SHORT.to_string(); + command_args.insert(insert_idx + 1, temp_file_path); + } else { + command_args[insert_idx + 1] = temp_file_path; + } + } + CommitMessageInputMethod::File(input_file) => { + let file_reader = BufReader::new(File::open(Path::new(&input_file))?); + let mut lines: Vec = file_reader.lines().map(|l| l.unwrap()).collect(); + lines.push("".to_string()); + authored_by.iter().for_each(|i| lines.push(i.clone())); + self.temp_file + .borrow_mut() + .write_all(lines.join("\n").as_bytes())?; + let insert_idx = + find_first_idx(command_args, &[GIT_FILE_OPT_SHORT, GIT_FILE_OPT_LONG]); + command_args[insert_idx + 1] = temp_file_path; + } + }; + + Ok(()) + } + fn get_active(&self) -> Result> { self.config .get(&namespaced("active")) @@ -303,14 +449,14 @@ impl GitTogether { #[cfg(test)] mod tests { - use super::*; - use std::collections::HashMap; use std::ops::Index; use author::{Author, AuthorParser}; use config::Config; + use super::*; + #[test] fn get_authors() { let config = MockConfig::new(&[ @@ -331,6 +477,7 @@ mod tests { let gt = GitTogether { config, author_parser, + temp_file: RefCell::new(NamedTempFile::new().unwrap()), }; assert!(gt.get_authors(&["jh"]).is_err()); @@ -384,6 +531,7 @@ mod tests { let mut gt = GitTogether { config, author_parser, + temp_file: RefCell::new(NamedTempFile::new().unwrap()), }; gt.set_active(&["jh"]).unwrap(); @@ -408,6 +556,7 @@ mod tests { let mut gt = GitTogether { config, author_parser, + temp_file: RefCell::new(NamedTempFile::new().unwrap()), }; gt.set_active(&["nn", "jh"]).unwrap(); @@ -432,6 +581,7 @@ mod tests { let mut gt = GitTogether { config, author_parser, + temp_file: RefCell::new(NamedTempFile::new().unwrap()), }; gt.set_active(&["nn", "jh"]).unwrap(); @@ -455,6 +605,7 @@ mod tests { let mut gt = GitTogether { config, author_parser, + temp_file: RefCell::new(NamedTempFile::new().unwrap()), }; gt.set_active(&["nn"]).unwrap(); @@ -476,6 +627,7 @@ mod tests { let mut gt = GitTogether { config, author_parser, + temp_file: RefCell::new(NamedTempFile::new().unwrap()), }; gt.rotate_active().unwrap(); @@ -499,6 +651,7 @@ mod tests { let gt = GitTogether { config, author_parser, + temp_file: RefCell::new(NamedTempFile::new().unwrap()), }; let all_authors = gt.all_authors().unwrap(); @@ -535,6 +688,7 @@ mod tests { let gt = GitTogether { config, author_parser, + temp_file: RefCell::new(NamedTempFile::new().unwrap()), }; assert_eq!(gt.is_signoff_cmd("commit"), true); @@ -552,6 +706,7 @@ mod tests { let gt = GitTogether { config, author_parser, + temp_file: RefCell::new(NamedTempFile::new().unwrap()), }; assert_eq!(gt.is_signoff_cmd("ci"), true);