~kaction/config

937f1dc71c050881291ae5251621b4395143987b — Dmitry Bogatov 3 months ago 148d52f + 0ad1a0c
Merge branch 'feature/provision-gitconfig' into next

* feature/provision-gitconfig:
  Drop expressions to rebuild rev-deps of git
  Recover git-logp script
  gitconfig: properly substitute reference to perl script
  manifest: enable ~/githooks in gitconfig
  Install git-bug post-commit script via nix-sys
  manifest: setup ~/githooks directory
  Provision ~/.gitconfig instead of embedding it into git

Closes: 3179c54
14 files changed, 104 insertions(+), 211 deletions(-)

M flake.nix
M manifest/default.nix
R universe/git/gitconfig.in => manifest/user/git.conf
A manifest/user/githooks/builder.py
A manifest/user/githooks/default.nix
R universe/git/gitignore => manifest/user/gitignore
R universe/githooks/post-commit/git-bug.sh => manifest/user/post-commit/git-bug.sh
D universe/git-bug/default.nix
D universe/git/builder.bash
D universe/git/default.nix
D universe/githooks/build.py
D universe/githooks/default.nix
D universe/githooks/run-directory.c
D universe/githooks/run-parts.sh
M flake.nix => flake.nix +0 -9
@@ 74,16 74,8 @@
          fasm-arch = call ./universe/fasm-arch;
          firewall = call ./universe/firewall;

          git = callPackage ./universe/git {
            # Imperfect, but compatible with old overlay-based code.
            git = pkgs.git.override { inherit (self') openssh curl; };
          };
          git-bug = callPackage ./universe/git-bug {
            inherit (pkgs.gitAndTools) git-bug;
          };
          git-recall = call ./universe/git-recall;
          git-merge-pr = call ./universe/git-merge-pr;
          githooks = call ./universe/githooks;

          mk-runscript = call ./universe/mk-runscript;



@@ 97,7 89,6 @@
          srht-ui = call ./universe/srht-ui;
          selectors = call ./universe/selectors;
          scripts = call ./universe/scripts;
          git-crypt = pkgs.git-crypt.override { inherit (self') git; };

          passphrase2pgp = call ./universe/passphrase2pgp;
          posixrc = callPackage ./universe/posixrc { };

M manifest/default.nix => manifest/default.nix +22 -3
@@ 1,5 1,5 @@
{ writeText, runCommandLocal, dhall-json, stdenv, callPackage, mk-passwd, doas
}:
{ writeText, runCommandLocal, dhall-json, stdenv, callPackage, mk-passwd,
  substituteAll, git, perl, doas, writeScript, execline }:
let
  symlink = path: {
    action = "symlink";


@@ 30,6 30,12 @@ let
    '';
    allowSubstitutes = false;
  };
  importManifest = path: args:
    let drv = callPackage path args;
    in builtins.fromJSON (builtins.readFile drv);
  manifest-githooks = importManifest ./user/githooks {
    githooks-root = "/home/kaction/githooks";
  };
  manifest = {
    base = let
      user-mkdir = {


@@ 48,6 54,19 @@ let
        "/home/kaction/.config" = user-mkdir;
        "/home/kaction/.config/dbxcli" = user-mkdir;
        "/home/kaction/.config/dbxcli/auth.json" = symlink ./secret/dbx.json;
        "/home/kaction/.config/git/config" = symlink (substituteAll {
          src = ./user/git.conf;
          logp = writeScript "logp" ''
            #!${execline}/bin/execlineb -WS0
            pipeline
              { git log -p --color=always --decorate=short $@ }
                less -R -p"^commit [0-9a-f]{40}"
          '';
          inherit perl git;
        });
        "/home/kaction/.config/git/ignore" = symlink ./user/gitignore;
        "/home/kaction/githooks/post-commit.d/500-git-bug" =
          symlink ./user/post-commit/git-bug.sh;

        "/etc/group" = symlink "${auth}/group";
        "/etc/gshadow" = { action = "unlink"; };


@@ 72,7 91,7 @@ let
        "/var/log/runit/activate/.keep" = touch;
        "/usr/local/bin/doas" = suid "${doas}/bin/doas";
        "/etc/xbps.d/xbps.conf" = symlink ./xbps.conf;
      };
      } // manifest-githooks;
    in writeText "manifest.json" (builtins.toJSON (stdenv.lib.fix f));
  };
in manifest.base

R universe/git/gitconfig.in => manifest/user/git.conf +2 -2
@@ 7,8 7,8 @@
[alias]
	log-fetched = log ORIG_HEAD.. --stat --no-merges
	new = !sh -c 'git log $1@{1}..$1@{0} "$@"'
	logp = !@logp@
	co = checkout
	logp = !@logp@
	pretend = diff --cached
	stat = !git --no-pager diff --stat
	over = !git --no-pager log --oneline --max-count=12


@@ 39,9 39,9 @@
[gc]
	writeCommitGraph = true
[core]
	hooksPath = ~/githooks
	commitGraph = true
	autolf = true
	hooksPath = @githooks@
[url "ssh://git@git.sr.ht"]
	pushInsteadOf = https://git.sr.ht
[url "ssh://git@github.com"]

A manifest/user/githooks/builder.py => manifest/user/githooks/builder.py +66 -0
@@ 0,0 1,66 @@
import sys
import os
import json
from textwrap import dedent

# Extracted directly from githooks(5). Sure, I use only small subset of
# them, but it is convenient to have everything ready to plug any hook
# in ad-hoc fashion.
hooks = [
    ("applypatch-msg"),
    ("pre-applypatch"),
    ("post-applypatch"),
    ("pre-commit"),
    ("pre-merge-commit"),
    ("prepare-commit-msg"),
    ("commit-msg"),
    ("post-commit"),
    ("pre-rebase"),
    ("post-checkout"),
    ("post-merge"),
    ("pre-push"),
    ("pre-receive"),
    ("update"),
    ("proc-receive"),
    ("post-receive"),
    ("post-update"),
    ("reference-transaction"),
    ("push-to-checkout"),
    ("pre-auto-gc"),
    ("post-rewrite"),
    ("sendemail-validate"),
    ("fsmonitor-watchman"),
    ("p4-changelist"),
    ("p4-prepare-changelist"),
    ("p4-post-changelist"),
    ("p4-pre-submit"),
    ("post-index-change"),
]

root = os.environ["root"]
data = os.environ["data"]
out = os.environ["out"]
debianutils = os.environ["debianutils"]
execline = os.environ["execline"]

manifest = {}
os.mkdir(data)

for hook in hooks:
    script = f"{data}/{hook}"
    with open(script, "w") as fp:
        fp.write(dedent(f"""
            #!{execline}/bin/execlineb -WS0
            {debianutils}/bin/run-parts --stdin --arg=$@ {root}/{hook}.d
        """[1:-1]))
    os.chmod(script, 0o555)
    manifest[f"{root}/{hook}"] = dict(action="symlink", path=script)
    # This is bad. I have uid hardcoded in multiple places.
    manifest[f"{root}/{hook}.d"] = {
        "action": "mkdir",
        "owner": 1000,
        "mode": "0700",
    }

with open(out, "w") as fp:
    json.dump(manifest, fp, indent=2)

A manifest/user/githooks/default.nix => manifest/user/githooks/default.nix +13 -0
@@ 0,0 1,13 @@
{ stdenv, python3, debianutils, execline, githooks-root }:
stdenv.mkDerivation {
  name = "manifest.json";
  outputs = [ "out" "data" ];
  nativeBuildInputs = [ python3 ];
  phases = [ "installPhase" ];
  installPhase = ''
    python3 ${./builder.py}
  '';

  root = githooks-root;
  inherit execline debianutils;
}

R universe/git/gitignore => manifest/user/gitignore +0 -0
R universe/githooks/post-commit/git-bug.sh => manifest/user/post-commit/git-bug.sh +1 -1
@@ 1,4 1,4 @@
#!@shell@
#!/bin/sh
set -eu
process_line() {
	IFS=": ,"

D universe/git-bug/default.nix => universe/git-bug/default.nix +0 -8
@@ 1,8 0,0 @@
{ git-bug }:
let
  stage1 = git-bug.overrideAttrs (_: {
    patchPhase = ''
      sed -i s,--global,--local,g repository/config_git.go
    '';
  });
in stage1

D universe/git/builder.bash => universe/git/builder.bash +0 -16
@@ 1,16 0,0 @@
#!/bin/bash -- source only
mkdir $out/config/git -p
cp -t $out -rs $git/*

chmod u+w $out/bin && unlink $out/bin/git

cat << EOF > $out/bin/git
#!${execline}/bin/execlineb -S0
export XDG_CONFIG_HOME $out/config
export FILTER_BRANCH_SQUELCH_WARNING 1
${blurdate}/bin/blurdate =hMs ${git}/bin/git \$@
EOF
chmod +x $out/bin/git

substituteAll $gitconfig $out/config/git/config
cp $gitignore $out/config/git/ignore

D universe/git/default.nix => universe/git/default.nix +0 -50
@@ 1,50 0,0 @@
{ git, writeScript, blurdate, perl, busybox, execline, runCommand, substituteAll, githooks }:
let
  stage1 = git.override {
    sendEmailSupport = true;
    coreutils = busybox;
    perlPackages = perl.pkgs;

    # Pure violence. git maintainer did not though about overrides hard
    # enough.
    #
    # Copied from pkgs/applications/version-management/git-and-tools.nix
    perlLibs = with perl.pkgs; [ LWP URI TermReadKey ];
    smtpPerlLibs = with perl.pkgs; [
      libnet
      NetSMTPSSL
      IOSocketSSL
      NetSSLeay
      AuthenSASL
      DigestHMAC
    ];
  };
  stage2 = stage1.overrideAttrs (old: {
    makeFlags = old.makeFlags ++ [ "SHELL_PATH=${busybox}/bin/sh" ];
    doInstallCheck = false;
  });
  stage3 = let
    env = {
      gitconfig = substituteAll {
        # This is tricky. Hooks scripts need git, and git includes
        # config which refers to hooks directory. Recursion.
        #
        # Here I break the loop by providing scripts with git that do
        # not have configuration embedded. This construction will
        # collapse when hook will need git for write operation and will
        # start complaining that user.name is not available.
        githooks = githooks.override { git = stage2; };
        logp = writeScript "logp" ''
          #!${execline}/bin/execlineb -WS0
          pipeline
            { git log -p --color=always --decorate=short $@ }
              less -R -p"^commit [0-9a-f]{40}"
        '';
        src = ./gitconfig.in;
      };
      gitignore = ./gitignore;
      git = stage2;
      inherit execline perl blurdate;
    };
  in runCommand git.name env "source ${./builder.bash}";
in stage3

D universe/githooks/build.py => universe/githooks/build.py +0 -20
@@ 1,20 0,0 @@
import os
import json

for key, value in os.environ.items():
    globals()[key] = value

hooks = json.loads(hooks)

for hook, scripts in hooks.items():
    os.makedirs(f"{out}/{hook}.d")
    for name, path in scripts.items():
        os.symlink(path, f"{out}/{hook}.d/{name}")
    with open(f"{out}/{hook}", "w") as fp:
        fp.write(f"""
#!{execline}/bin/execlineb -WS0
{rundir} {out}/{hook}.d $@
"""[1:])
    os.chmod(f"{out}/{hook}", 0o777)



D universe/githooks/default.nix => universe/githooks/default.nix +0 -52
@@ 1,52 0,0 @@
{ stdenv, python3, writeScript, substituteAll, busybox, debianutils, lib, git
, git-bug, execline }:
let
  run-parts = stdenv.mkDerivation {
    name = "run-parts";
    src = debianutils;
    phases = [ "installPhase" ];
    installPhase = ''
      cp $src/bin/run-parts $out
    '';
  };

  # run-parts has brain-damaged interface for passing arguments to
  # script.
  rundir = stdenv.mkDerivation {
    name = "run-directory";
    src = ./run-directory.c;
    phases = [ "installPhase" ];
    installPhase = ''
      cc -D'RUN_PARTS="${run-parts}"' $src -o $out
    '';
  };

  make-shell = { path ? [ ] }:
    let
      path' = if builtins.isString path then
        path
      else
        lib.makeBinPath (path ++ [ busybox ]);
    in writeScript "busybox-shell" ''
      #!${execline}/bin/execlineb -WS0
      export PATH ${path'}
      ${busybox}/bin/sh $@
    '';

  hooks = {
    post-commit.git-bug = substituteAll {
      shell = make-shell { path = [ git git-bug ]; };
      src = ./post-commit/git-bug.sh;
      isExecutable = true;
    };
  };

in stdenv.mkDerivation {
  inherit execline rundir;

  name = "githooks";
  buildInputs = [ python3 ];
  phases = [ "installPhase" ];
  hooks = builtins.toJSON hooks;
  installPhase = "python3 ${./build.py}";
}

D universe/githooks/run-directory.c => universe/githooks/run-directory.c +0 -34
@@ 1,34 0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char **argv)
{
	const char **args;
	const char *directory;
	int i;
	if (argc < 2) {
		fputs("usage: run-directory <directory> [ARGS ...]\n", stderr);
		return 1;
	}
	directory = argv[1];
	argv++;

	args = malloc(sizeof(char *) * (2 + argc * 2));
	if (!args) {
		fputs("fatal: malloc() failed", stderr);
		return 1;
	}

	i = 0;
	args[i++] = RUN_PARTS;
	while (*++argv) {
		args[i++] = "--arg";
		args[i++] = *argv;
	}
	args[i++] = "--";
	args[i++] = directory;
	args[i++] = NULL;

	return execvp(RUN_PARTS, (char *const *)args);
}

D universe/githooks/run-parts.sh => universe/githooks/run-parts.sh +0 -16
@@ 1,16 0,0 @@
#!@busybox@/bin/sh
set -eu
export PATH=@busybox@/bin
export LC_COLLATION=POSIX

directory=$1; shift
tempfile=$(mktemp)
cat > "${tempfile}"
cleanup() { rm -f "${tempfile}"; }
trap cleanup 0

for script in "${directory}"/* ; do
	if [ -x "${script}" ] && [ -f "${script}" ] ; then