From dd7d702010409dd8810872ce77f8983e3a6d682f Mon Sep 17 00:00:00 2001 From: Evan Pratten Date: Mon, 19 Feb 2024 15:08:27 -0500 Subject: [PATCH] tweak git and ssh --- configs/git/.gitconfig | 2 + configs/shells/zsh/.zshrc | 4 + configs/ssh/config | 3 + scripts/git-detect-fake-authors | 6 ++ scripts/git-log-sqlite | 182 ++++++++++++++++++++++++++++++++ 5 files changed, 197 insertions(+) create mode 100755 scripts/git-detect-fake-authors create mode 100755 scripts/git-log-sqlite diff --git a/configs/git/.gitconfig b/configs/git/.gitconfig index 57f4a36..37ce2f3 100644 --- a/configs/git/.gitconfig +++ b/configs/git/.gitconfig @@ -47,3 +47,5 @@ [credential "https://gist.github.com"] helper = helper = !/usr/bin/gh auth git-credential +[push] + autoSetupRemote = true diff --git a/configs/shells/zsh/.zshrc b/configs/shells/zsh/.zshrc index c81007e..eae7fec 100644 --- a/configs/shells/zsh/.zshrc +++ b/configs/shells/zsh/.zshrc @@ -52,3 +52,7 @@ export SDKMAN_DIR="$HOME/.sdkman" # Flutter [[ -s "$HOME/pkg/flutter/bin" ]] && export PATH="$HOME/pkg/flutter/bin:$PATH" + +# Rye +[[ -s "$HOME/.rye/env" ]] && source "$HOME/.rye/env" +. "$HOME/.cargo/env" diff --git a/configs/ssh/config b/configs/ssh/config index 1e05045..831b512 100644 --- a/configs/ssh/config +++ b/configs/ssh/config @@ -5,6 +5,7 @@ Host * !*.github.com !github.com IdentityFile %d/.ssh/id_ed25519 IdentityFile %d/.ssh/id_rsa # VisualHostKey yes + VerifyHostKeyDNS ask # Github SSH adapter for restricted networks Host github.com gist.github.com @@ -91,6 +92,8 @@ Host td-prod td-prod2 td-prod3 td-prod4 User guru Host w6421 User "guru-domain\\epratten" + #RequestTTY yes + # RemoteCommand "C:\Program Files\Git\bin\bash.exe" --login # Personal Infra Host oci-arm diff --git a/scripts/git-detect-fake-authors b/scripts/git-detect-fake-authors new file mode 100755 index 0000000..5db63e8 --- /dev/null +++ b/scripts/git-detect-fake-authors @@ -0,0 +1,6 @@ +#! /bin/bash +# This script finds every time someone makes a commit under someone else's name +# NOTE: This includes co-authoring +set -e + +git-log-sqlite -q 'SELECT committer as Real, author as Fake, count(*) as Count FROM commits WHERE author NOT LIKE committer GROUP BY author, committer ORDER BY count DESC;' \ No newline at end of file diff --git a/scripts/git-log-sqlite b/scripts/git-log-sqlite new file mode 100755 index 0000000..d10782f --- /dev/null +++ b/scripts/git-log-sqlite @@ -0,0 +1,182 @@ +#! /usr/bin/env python3 +import subprocess +import sqlite3 +import argparse +import sys +import logging +from pathlib import Path +from typing import Dict + +logger = logging.getLogger(__name__) + +FIELDS = { + "ct": "timestamp", + "aN": "author", + "aE": "email", + "cN": "committer", + "cE": "committer_email", + "s": "subject", + "b": "body", + "N": "notes", +} + + +def read_properties() -> Dict[str, Dict[str, str]]: + output = {} + for field in FIELDS: + # Construct the log request + format_str = f"%H %{field}%x00" + + # Get the results + repo_results = subprocess.run( + ["git", "log", f"--format=format:{format_str}"], + capture_output=True, + text=True, + ).stdout + submodule_results = subprocess.run( + [ + "git", + "submodule", + "foreach", + "git", + "log", + f"--format=format:{format_str}", + ], + capture_output=True, + text=True, + ).stdout + + # Parse the results + all_results = repo_results + submodule_results + all_results = all_results.split("\x00") + for result in all_results: + if " " not in result or result == "": + continue + commit_hash, value = result.split(" ", 1) + if commit_hash.startswith("Entering"): + continue + if commit_hash.startswith("\n"): + commit_hash = commit_hash[1:] + if commit_hash not in output: + output[commit_hash] = {} + output[commit_hash][field] = value + + return output + + +def create_table(cursor: sqlite3.Cursor) -> None: + sql = "CREATE TABLE IF NOT EXISTS commits (hash TEXT PRIMARY KEY, " + for field in FIELDS.values(): + ty = "TEXT" + if field == "timestamp": + ty = "INTEGER" + if field == "hash": + ty = "TEXT PRIMARY KEY" + + sql += f"{field} {ty}, " + sql = sql[:-2] + ")" + logger.debug(f"Creating table with SQL: {sql}") + cursor.execute(sql) + + +def main() -> int: + # Handle program arguments + ap = argparse.ArgumentParser( + prog="git-log-sqlite", description="Interact with the git log using SQL" + ) + ap.add_argument( + "--dump", + help="Path to a sqlite3 database file to dump contents to. DELETES EXISTING FILE", + type=Path, + ) + ap.add_argument( + "--interactive", + "-i", + help="Start an interactive SQL session", + action="store_true", + ) + ap.add_argument("--query", "-q", help="Run a query and print the results") + ap.add_argument("--no-header", help="Do not print the header", action="store_true") + ap.add_argument("--mode", help="Set the mode for the sqlite3 command", default="table") + ap.add_argument( + "-v", "--verbose", help="Enable verbose logging", action="store_true" + ) + args = ap.parse_args() + + # Configure logging + logging.basicConfig( + level=logging.DEBUG if args.verbose else logging.INFO, + format="%(levelname)s: %(message)s", + ) + + # Interactive mode and query mode are mutually exclusive + if args.interactive and args.query: + logger.error("Interactive mode and query mode are mutually exclusive") + return 1 + + # If the user didn't specify anything, print the help message + if not (args.interactive or args.query): + ap.print_help() + return 1 + + # Read the properties + commits = read_properties() + logger.debug(f"Read {len(commits)} commits") + + # Open a connection to the database + if args.dump: + args.dump.parent.mkdir(parents=True, exist_ok=True) + args.dump.unlink(missing_ok=True) + conn = sqlite3.connect(args.dump if args.dump else ":memory:") + cursor = conn.cursor() + + # Create a table to store the data + create_table(cursor) + + # Insert the data into the table + rows = list(commits.items()) + rows.sort(key=lambda x: x[1]["ct"]) + for commit_hash, data in rows: + sql = "INSERT INTO commits VALUES (" + ",".join(["?"] * (len(FIELDS) + 1)) + ")" + values = [commit_hash] + [data.get(field, None) for field in FIELDS.keys()] + cursor.execute(sql, values) + + # Commit the changes + conn.commit() + + # If just dumping, we are done + if args.dump: + conn.close() + return 0 + + # Dump to a temp file + import tempfile + + temp_file = Path(tempfile.mkstemp()[1]) + temp_conn = sqlite3.connect(temp_file) + temp_conn.executescript("\n".join(conn.iterdump())) + temp_conn.commit() + conn.close() + + # Build the base sqlite command + sqlite_cmd = ["sqlite3", "--cmd", f".mode {args.mode}"] + if not args.no_header: + sqlite_cmd.append("--cmd") + sqlite_cmd.append(".headers on") + + # If running a query, do so + if args.query: + subprocess.run(sqlite_cmd + [temp_file, args.query]) + + # If running interactively, do so + if args.interactive: + subprocess.run(sqlite_cmd + ["--interactive", temp_file]) + + # Delete the temp file + temp_file.unlink() + + return 0 + + +if __name__ == "__main__": + sys.exit(main())