Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,10 @@ Cargo.lock
.kiro/
.agents/
.idea/
skills-lock.json
skills-lock.json

# Added by cargo
#
# already existing elements were commented out

#/target
18 changes: 18 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "gitkit"
version = "0.1.0"
edition = "2021"
description = "Standalone CLI for configuring git repos — hooks, .gitignore, and .gitattributes"
license = "MIT"
repository = "https://github.com/JheisonMB/gitkit"
keywords = ["git", "hooks", "cli", "gitignore", "gitattributes"]
categories = ["command-line-utilities", "development-tools"]

[[bin]]
name = "gitkit"
path = "src/main.rs"

[dependencies]
anyhow = "1"
clap = { version = "4", features = ["derive"] }
ureq = "2"
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ Standalone CLI for configuring git repos — hooks, .gitignore, and .gitattribut
curl -fsSL https://raw.githubusercontent.com/JheisonMB/gitkit/main/install.sh | sh
```

**Windows (PowerShell):**

```powershell
irm https://raw.githubusercontent.com/JheisonMB/gitkit/main/install.ps1 | iex
```

### Via cargo

```bash
Expand Down Expand Up @@ -50,7 +56,7 @@ gitkit hooks init pre-push "cargo test"
gitkit hooks list

# Generate a .gitignore
gitkit ignore add rust macos
gitkit ignore add rust,vscode

# Apply line endings preset
gitkit attributes init
Expand All @@ -67,8 +73,9 @@ gitkit attributes init
| `gitkit hooks remove <hook>` | Remove a hook |
| `gitkit hooks show <hook>` | Show hook content |
| `gitkit ignore add <templates>` | Generate .gitignore via gitignore.io |
| `gitkit ignore list` | List available templates |
| `gitkit ignore list [filter]` | List available templates |
| `gitkit attributes init` | Apply line endings preset |
| `gitkit config apply <preset>` | Apply git config preset (defaults, advanced, delta) |

---

Expand Down
75 changes: 75 additions & 0 deletions install.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# install.ps1 — download and install gitkit on Windows
# Usage: irm https://raw.githubusercontent.com/JheisonMB/gitkit/main/install.ps1 | iex
#
# Options (set as env vars before running):
# $env:VERSION = "0.1.0" # pin a specific version
# $env:INSTALL_DIR = "C:\my\bin" # custom install directory

$ErrorActionPreference = "Stop"

$Repo = "JheisonMB/gitkit"
$Binary = "gitkit.exe"
$Target = "x86_64-pc-windows-msvc"
$InstallDir = if ($env:INSTALL_DIR) { $env:INSTALL_DIR } else { "$env:USERPROFILE\.local\bin" }

function Info($label, $msg) {
Write-Host " " -NoNewline
Write-Host $label -ForegroundColor Blue -NoNewline
Write-Host " $msg"
}

function Fail($msg) {
Write-Host " error: $msg" -ForegroundColor Red
exit 1
}

# --- resolve version ---
if ($env:VERSION) {
$Tag = "v$($env:VERSION)"
Info "version" "$Tag (pinned)"
} else {
$latest = Invoke-RestMethod "https://api.github.com/repos/$Repo/releases/latest"
$Tag = $latest.tag_name
if (-not $Tag) { Fail "Could not resolve latest release tag" }
Info "version" "$Tag (latest)"
}

# --- download ---
$Archive = "gitkit-$Tag-$Target.zip"
$Url = "https://github.com/$Repo/releases/download/$Tag/$Archive"
$Tmp = Join-Path $env:TEMP "gitkit-install"
New-Item -ItemType Directory -Force -Path $Tmp | Out-Null

Info "download" $Url
try {
Invoke-WebRequest -Uri $Url -OutFile "$Tmp\$Archive" -UseBasicParsing
} catch {
Fail "Download failed: $_`nURL: $Url"
}

# --- extract ---
Expand-Archive -Path "$Tmp\$Archive" -DestinationPath $Tmp -Force
$extracted = Join-Path $Tmp $Binary
if (-not (Test-Path $extracted)) { Fail "Binary not found in archive" }

# --- install ---
New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
Copy-Item $extracted "$InstallDir\$Binary" -Force
Info "installed" "$InstallDir\$Binary"

# --- ensure PATH ---
$userPath = [Environment]::GetEnvironmentVariable("PATH", "User")
if ($userPath -notlike "*$InstallDir*") {
[Environment]::SetEnvironmentVariable("PATH", "$InstallDir;$userPath", "User")
$env:PATH = "$InstallDir;$env:PATH"
Info "updated" "User PATH"
}

# --- cleanup ---
Remove-Item $Tmp -Recurse -Force

# --- verify ---
$ver = & "$InstallDir\$Binary" --version 2>$null
Info "done" $ver
Write-Host ""
Info "ready" "Run 'gitkit hooks init commit-msg conventional-commits' to get started!"
82 changes: 82 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/bin/sh
# install.sh — download and install gitkit from GitHub Releases
# Usage: curl -fsSL https://raw.githubusercontent.com/JheisonMB/gitkit/main/install.sh | sh
set -eu

REPO="JheisonMB/gitkit"
BINARY="gitkit"
INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/bin}"

info() { printf ' \033[1;34m%s\033[0m %s\n' "$1" "$2"; }
error() { printf ' \033[1;31merror:\033[0m %s\n' "$1" >&2; exit 1; }

# --- detect OS ---
OS="$(uname -s)"
case "$OS" in
Linux*) OS_TARGET="unknown-linux-musl" ;;
Darwin*) OS_TARGET="apple-darwin" ;;
*) error "Unsupported OS: $OS (only Linux and macOS are supported)" ;;
esac

# --- detect arch ---
ARCH="$(uname -m)"
case "$ARCH" in
x86_64|amd64) ARCH_TARGET="x86_64" ;;
arm64|aarch64) ARCH_TARGET="aarch64" ;;
*) error "Unsupported architecture: $ARCH" ;;
esac

TARGET="${ARCH_TARGET}-${OS_TARGET}"
info "platform" "$TARGET"

TMPDIR="$(mktemp -d)"
trap 'rm -rf "$TMPDIR"' EXIT

# --- resolve version ---
if [ -n "${VERSION:-}" ]; then
TAG="v$VERSION"
info "version" "$TAG (pinned)"
else
TAG=$(curl -fsSL -o /dev/null -w '%{url_effective}' "https://github.com/$REPO/releases/latest" | rev | cut -d'/' -f1 | rev)
[ -z "$TAG" ] && error "Could not resolve latest release tag"
info "version" "$TAG (latest)"
fi

# --- download ---
ARCHIVE="${BINARY}-${TAG}-${TARGET}.tar.gz"
URL="https://github.com/$REPO/releases/download/${TAG}/${ARCHIVE}"

info "download" "$URL"
HTTP_CODE=$(curl -fSL -w '%{http_code}' -o "$TMPDIR/$ARCHIVE" "$URL" 2>/dev/null) || true
[ "$HTTP_CODE" = "200" ] || error "Download failed (HTTP $HTTP_CODE). Check that $TAG exists for $TARGET at:\n $URL"

# --- extract ---
tar xzf "$TMPDIR/$ARCHIVE" -C "$TMPDIR"
[ -f "$TMPDIR/$BINARY" ] || error "Binary not found in archive"

# --- install ---
mkdir -p "$INSTALL_DIR"
mv "$TMPDIR/$BINARY" "$INSTALL_DIR/$BINARY"
chmod +x "$INSTALL_DIR/$BINARY"
info "installed" "$INSTALL_DIR/$BINARY"

# --- ensure PATH ---
case ":$PATH:" in
*":$INSTALL_DIR:"*) ;;
*)
export PATH="$INSTALL_DIR:$PATH"
for profile in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile"; do
if [ -f "$profile" ]; then
if ! grep -q "export PATH=\"$INSTALL_DIR:\$PATH\"" "$profile" 2>/dev/null; then
printf '\n# Added by gitkit installer\nexport PATH="%s:$PATH"\n' "$INSTALL_DIR" >> "$profile"
info "updated" "$profile"
fi
fi
done
;;
esac

# --- verify ---
info "done" "$($INSTALL_DIR/$BINARY --version 2>/dev/null || echo "$BINARY installed")"
echo ""
info "ready" "Run 'gitkit hooks init commit-msg conventional-commits' to get started!"
52 changes: 52 additions & 0 deletions src/attributes/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use anyhow::{Context, Result};
use clap::Subcommand;
use std::fs;

use crate::utils::{confirm, find_repo_root};

const PRESET: &str = "* text=auto eol=lf\n";

#[derive(Subcommand)]
pub enum AttributesCommand {
/// Apply line endings preset to .gitattributes
Init {
#[arg(short, long)]
yes: bool,
#[arg(short, long)]
force: bool,
#[arg(long)]
dry_run: bool,
},
}

pub fn run(cmd: AttributesCommand) -> Result<()> {
let AttributesCommand::Init {
yes,
force,
dry_run,
} = cmd;

let root = find_repo_root()?;
let path = root.join(".gitattributes");

if path.exists() && !force {
if !confirm(".gitattributes already exists. Overwrite?", yes) {
println!("Aborted.");
return Ok(());
}
if !dry_run {
let backup = root.join(".gitattributes.bak");
std::fs::copy(&path, &backup).context("Failed to backup .gitattributes")?;
println!("Backed up to {}", backup.display());
}
}

if dry_run {
println!("[dry-run] Would write .gitattributes:\n{PRESET}");
return Ok(());
}

fs::write(&path, PRESET).context("Failed to write .gitattributes")?;
println!("Applied line endings preset to .gitattributes.");
Ok(())
}
Loading
Loading