Git


1 Overview

🎯 git/cfg.personal.conf
[user]
	name = Linus Arver
	email = linus@ucla.edu
[clone]
	defaultRemoteName = u
[color]
	branch = auto
	diff = auto
	status = auto
[color "diff"]
	meta = yellow bold
	commit = blue bold reverse
[color "status"]
	untracked = yellow bold
[commit]
	cleanup = scissors
[core]
	editor = ~/syscfg/script/emacsclient-tty.sh
	excludesFile = ~/.gitignore
	hooksPath = ~/syscfg/script/git/hooks
[diff]
	algorithm = histogram
	colorMoved = default
	colorMovedWS = allow-indentation-change
	context = 5
	tool = difftastic
[difftool]
	prompt = false
[difftool "difftastic"]
	cmd = difft "$LOCAL" "$REMOTE"
[gui]
	spellingdictionary = none
[init]
	defaultBranch = main
[merge]
	conflictStyle = zdiff3
[pager]
	difftool = true
[push]
	default = simple
	autoSetupRemote = true
[rebase]
	updateRefs = true
[rerere]
	enabled = true
[sendemail]
	aliasesFile = ~/.git-sendemail-aliases
	aliasFileType = mailrc
	chainReplyTo = false
	sendmailCmd = "gmi send --quiet -t -C ~/mail/linusarver@gmail.com"
	suppressCc = self
[tag]
	sort = taggerdate
[tig]
	vertical-split = false
Makefile-git
git:
	ln -fs ${C}/git/cfg.personal.conf ${H}/.gitconfig
	ln -fs ${C}/git/sendemail-aliases ${H}/.git-sendemail-aliases

2 Email

🎯 git/sendemail-aliases
alias git git@vger.kernel.org

3 Diffs

By default running git diff gives you paged output, and sometimes it can be tricky to remember whether the diff was of the worktree or the index (what's been staged, if you passed the -c flag).

It would be nice if you could put a vertical label showing "worktree" or "index" to tell you which diff you were looking at. Below is a Rust program which does this (it uses the label "CHANGED" and "STAGED" instead of "worktree" or "index").

🎯 gd/src/main.rs
use clap::{Arg, ArgAction, Command};
use colored::*;
use std::borrow::Cow;
use std::process::Command as SCommand;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let cmd = clap::Command::new("gd")
        .version("v0.0")
        .propagate_version(true)
        .subcommand_required(true)
        .arg_required_else_help(true)
        .subcommand(
            Command::new("diff")
                .about("Show git unstaged and staged diffs.")
                .arg(
                    Arg::new("width")
                        .long("width")
                        .value_name("NUM")
                        .default_value("80")
                        .help("With of the terminal screen; used for coloring in missing trailing whitespace for better legibility. Default 80.")
                        .takes_value(true)
                        .required(false),
                )
                .arg(
                    Arg::new("staged")
                        .long("staged")
                        .help("Whether to get staged diff.")
                        .action(ArgAction::SetTrue),
                ),
        );
    let matches = cmd.get_matches();
    match matches.subcommand_name() {
        Some("diff") => {
            if let Some(m) = matches.subcommand_matches("diff") {
                let width = m
                    .get_one::<String>("width")
                    .unwrap()
                    .parse::<usize>()
                    .unwrap();
                let staged = m.get_one::<bool>("staged").unwrap();
                let diff = if *staged {
                    SCommand::new("git")
                        .args(["diff", "--staged"])
                        .output()
                        .expect("git command failed to start")
                } else {
                    SCommand::new("git")
                        .args(["diff"])
                        .output()
                        .expect("git command failed to start")
                };

                colored::control::set_override(true);
                show_diff(
                    &diff.stdout,
                    &width,
                    if *staged {
                        " STAGED ---------------------- "
                    } else {
                        " CHANGED --------------------- "
                    },
                    *staged,
                );
            }
        }
        _ => unreachable!("clap should ensure we don't get here"),
    }
    Ok(())
}

fn show_diff(output: &Vec<u8>, width: &usize, vlabel: &str, staged: bool) -> () {
    // We convert tabs into 4 spaces. This should ideally only
    // affect leading indentation, but we are too lazy to fix this
    // because 99.999% of the time literal tabs are only found in
    // the leading indentation anyway.
    let diff_output = String::from_utf8_lossy(output).replace("\t", "    ");

    // Split output lines, then for each line: (1) prepend the
    // vertical label char and (2) depending on leading +/- char
    // colorize the text fg and bg.

    let divider = " ".on_truecolor(0, 0, 0);

    let mut i = 0;
    for line in diff_output.lines() {
        let mut words_iter = line.split_ascii_whitespace();
        let (eline, escaped) = escape(line);

        let cline = if line.len() > 0 {
            if line.chars().nth(0).unwrap().is_ascii_whitespace() {
                format!(" {}", eline).normal()
            } else {
                match words_iter.next() {
                    Some("diff") => format!(" {}", eline)
                        .bold()
                        .truecolor(255, 255, 0)
                        .on_truecolor(85, 85, 51),
                    Some("index") => format!(" {}", eline)
                        .bold()
                        .truecolor(255, 255, 0)
                        .on_truecolor(85, 85, 51),
                    Some("---") => format!(" {}", eline)
                        .bold()
                        .truecolor(255, 255, 0)
                        .on_truecolor(85, 85, 51),
                    Some("+++") => format!(" {}", eline)
                        .bold()
                        .truecolor(255, 255, 0)
                        .on_truecolor(85, 85, 51),

                    // This is not identical to git-diff (the latter does
                    // not colorize the entire line), but this is close
                    // enough.
                    Some("@@") => format!(" {}", eline).cyan(),

                    // It could be that the word looks like "+foo" if "foo" is at the
                    // beginning of the eline. In this case we have to check for the
                    // first letter.
                    Some(w) => match &w[0..1] {
                        "+" => format!(" {}", eline)
                            .truecolor(204, 255, 204)
                            .on_truecolor(51, 85, 51)
                            .bold(),
                        "-" => format!(" {}", eline)
                            .truecolor(255, 204, 204)
                            .on_truecolor(85, 51, 51)
                            .bold(),
                        _ => format!(" {}", eline).normal(),
                    },

                    None => format!(" {}", eline).normal(),
                }
            }
        } else {
            "".normal()
        };

        // vlabel_part is surrounded with 3 spaces, for a total of 4 chars. We
        // also have to bump the width because each escaped character fools
        // println!() into thinking that the string is 1 char larger than it
        // really is.
        let rwidth = width - 4 + escaped;
        let vlabel_part = match &vlabel[i..i + 1] {
            " " => {
                if staged {
                    "   ".truecolor(255, 0, 255).on_truecolor(81, 51, 81)
                } else {
                    "   ".truecolor(0, 255, 255).on_truecolor(51, 81, 81)
                }
            }
            "-" => {
                if staged {
                    " \u{2503} ".truecolor(255, 0, 255).on_truecolor(81, 51, 81)
                } else {
                    " \u{2503} ".truecolor(0, 255, 255).on_truecolor(51, 81, 81)
                }
            }
            c => {
                if staged {
                    format!(" {} ", c)
                        .truecolor(255, 0, 255)
                        .on_truecolor(81, 51, 81)
                } else {
                    format!(" {} ", c)
                        .truecolor(0, 255, 255)
                        .on_truecolor(51, 81, 81)
                }
            }
        };
        println!("{}{}{:rwidth$}", vlabel_part, divider, cline);
        i = (i + 1) % vlabel.len();
    }
}

// Escapes a string to be fed into println!(). E.g., "foo\bar" becomes
// "foo\\bar".
//
// Adapted from
// https://github.com/njaard/sonnerie/blob/e6b82eed3d5ba53c2e667172985df9a967056b5d/escape_string/src/lib.rs#L166.
fn escape<'a>(text: &'a str) -> (Cow<'a, str>, usize) {
    let bytes = text.as_bytes();

    let mut owned = None;
    let mut escaped = 0;

    for pos in 0..bytes.len() {
        let special = match bytes[pos] {
            b'%' => Some(b'%'),
            b'\\' => Some(b'\\'),
            _ => None,
        };
        if let Some(s) = special {
            if owned.is_none() {
                owned = Some(bytes[0..pos].to_owned());
            }
            owned.as_mut().unwrap().push(s);
            owned.as_mut().unwrap().push(s);
            escaped = escaped + 1;
        } else if let Some(owned) = owned.as_mut() {
            owned.push(bytes[pos]);
        }
    }

    if let Some(owned) = owned {
        (
            unsafe { Cow::Owned(String::from_utf8_unchecked(owned)) },
            escaped,
        )
    } else {
        (
            unsafe { Cow::Borrowed(std::str::from_utf8_unchecked(bytes)) },
            escaped,
        )
    }
}

3.1 Build system

🎯 gd/Makefile
all:
	cargo install --profile release --path .
build:
	cargo build
test:
	cargo test
🎯 gd/Cargo.toml
[package]
name = "gd"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
clap = { version = "3.1.0", features = ["cargo"]}
colored = "2"
🎯 Cargo.toml
[workspace]
members = [
    "gd"
]

resolver = "2"

[profile.release]
# Strip all symbols.
strip = true
# Bring in other optimizations to reduce binary size.
debug = false
opt-level = 'z'
codegen-units = 1
lto = true
panic = 'abort'

Page metrics

Tangled files (6)

  1. Cargo.toml
  2. gd/Cargo.toml
  3. gd/Makefile
  4. gd/src/main.rs
  5. git/cfg.personal.conf
  6. git/sendemail-aliases

Named cells (1)

  1. Makefile-git