Compare commits

...

23 Commits

Author SHA1 Message Date
github-actions[bot]
ee47821089 chore: update pre-commit hooks (#115) 2025-06-02 08:16:06 +03:00
github-actions[bot]
b834ce04f7 chore: update pre-commit hooks (#114) 2025-05-29 14:54:33 +03:00
github-actions[bot]
6a62d73d7f chore: update pre-commit hooks (#113) 2025-05-26 13:15:01 +03:00
github-actions[bot]
440842ed34 chore: update pre-commit hooks (#112) 2025-05-22 08:29:51 +03:00
renovate[bot]
d0563e4a29 chore(deps): update node.js to v22.16.0 (#111) 2025-05-22 01:11:53 +03:00
github-actions[bot]
bc404bfbea chore: update pre-commit hooks (#110) 2025-05-19 08:27:54 +03:00
923f881725 chore(config): change git merge conflictStyle 2025-05-16 21:50:29 +03:00
ccc5903290 chore(config): updated zed settings
Signed-off-by: Ismo Vuorinen <ismo@ivuorinen.net>
2025-05-16 21:49:55 +03:00
renovate[bot]
786efc48fa chore(deps): update node.js to v22.15.1 (#109) 2025-05-15 20:09:12 +03:00
2a11a28422 chore(repo): tweak install.conf.yaml
Signed-off-by: Ismo Vuorinen <ismo@ivuorinen.net>
2025-05-15 16:39:15 +03:00
812a27ea61 chore(config): updated zed settings
Signed-off-by: Ismo Vuorinen <ismo@ivuorinen.net>
2025-05-15 16:38:49 +03:00
e73e61f01b chore(config): added git-profile completions
Signed-off-by: Ismo Vuorinen <ismo@ivuorinen.net>
2025-05-15 16:38:23 +03:00
314679b4fc chore(deps): updated Brewfile
Signed-off-by: Ismo Vuorinen <ismo@ivuorinen.net>
2025-05-15 16:37:59 +03:00
github-actions[bot]
516b27384a chore: update pre-commit hooks (#108)
Co-authored-by: ivuorinen <11024+ivuorinen@users.noreply.github.com>
2025-05-12 12:28:03 +03:00
github-actions[bot]
9e1af3053d chore: update pre-commit hooks (#107)
Co-authored-by: ivuorinen <11024+ivuorinen@users.noreply.github.com>
2025-05-08 09:51:32 +03:00
github-actions[bot]
9e4f8741b3 chore: update pre-commit hooks (#106)
Co-authored-by: ivuorinen <11024+ivuorinen@users.noreply.github.com>
2025-05-05 11:37:52 +03:00
c0995c1b49 chore(config): zed: settings update 2025-05-03 02:32:38 +03:00
c9f1e824c3 chore(bin): fish support shared.sh and dfm 2025-05-03 02:32:01 +03:00
3d301daeb1 chore: remove x-dupers.pl 2025-05-03 02:29:48 +03:00
8b4198dc90 chore(lint): shfmt local/bin/* 2025-05-03 02:15:04 +03:00
66461f9b1b chore(config): zed: update config 2025-05-03 02:14:19 +03:00
80851d1efd chore(config): vim: fix ctrl-s, ctrl-p 2025-05-03 02:13:45 +03:00
github-actions[bot]
c457c0f3ab chore: update pre-commit hooks (#105)
Co-authored-by: ivuorinen <11024+ivuorinen@users.noreply.github.com>
2025-05-01 11:15:18 +03:00
13 changed files with 355 additions and 298 deletions

2
.nvmrc
View File

@@ -1 +1 @@
22.15.0 22.16.0

View File

@@ -23,13 +23,13 @@ repos:
args: [--autofix, --no-sort-keys] args: [--autofix, --no-sort-keys]
- repo: https://github.com/igorshubovych/markdownlint-cli - repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.44.0 rev: v0.45.0
hooks: hooks:
- id: markdownlint - id: markdownlint
args: [-c, .markdownlint.json, --fix] args: [-c, .markdownlint.json, --fix]
- repo: https://github.com/adrienverge/yamllint - repo: https://github.com/adrienverge/yamllint
rev: v1.37.0 rev: v1.37.1
hooks: hooks:
- id: yamllint - id: yamllint
@@ -49,7 +49,7 @@ repos:
- id: actionlint - id: actionlint
- repo: https://github.com/renovatebot/pre-commit-hooks - repo: https://github.com/renovatebot/pre-commit-hooks
rev: 39.261.4 rev: 40.36.8
hooks: hooks:
- id: renovate-config-validator - id: renovate-config-validator

View File

@@ -0,0 +1,176 @@
# fish completion for git-profile -*- shell-script -*-
function __git_profile_debug
set -l file "$BASH_COMP_DEBUG_FILE"
if test -n "$file"
echo "$argv" >> $file
end
end
function __git_profile_perform_completion
__git_profile_debug "Starting __git_profile_perform_completion"
# Extract all args except the last one
set -l args (commandline -opc)
# Extract the last arg and escape it in case it is a space
set -l lastArg (string escape -- (commandline -ct))
__git_profile_debug "args: $args"
__git_profile_debug "last arg: $lastArg"
set -l requestComp "$args[1] __complete $args[2..-1] $lastArg"
__git_profile_debug "Calling $requestComp"
set -l results (eval $requestComp 2> /dev/null)
# Some programs may output extra empty lines after the directive.
# Let's ignore them or else it will break completion.
# Ref: https://github.com/spf13/cobra/issues/1279
for line in $results[-1..1]
if test (string trim -- $line) = ""
# Found an empty line, remove it
set results $results[1..-2]
else
# Found non-empty line, we have our proper output
break
end
end
set -l comps $results[1..-2]
set -l directiveLine $results[-1]
# For Fish, when completing a flag with an = (e.g., <program> -n=<TAB>)
# completions must be prefixed with the flag
set -l flagPrefix (string match -r -- '-.*=' "$lastArg")
__git_profile_debug "Comps: $comps"
__git_profile_debug "DirectiveLine: $directiveLine"
__git_profile_debug "flagPrefix: $flagPrefix"
for comp in $comps
printf "%s%s\n" "$flagPrefix" "$comp"
end
printf "%s\n" "$directiveLine"
end
# This function does two things:
# - Obtain the completions and store them in the global __git_profile_comp_results
# - Return false if file completion should be performed
function __git_profile_prepare_completions
__git_profile_debug ""
__git_profile_debug "========= starting completion logic =========="
# Start fresh
set --erase __git_profile_comp_results
set -l results (__git_profile_perform_completion)
__git_profile_debug "Completion results: $results"
if test -z "$results"
__git_profile_debug "No completion, probably due to a failure"
# Might as well do file completion, in case it helps
return 1
end
set -l directive (string sub --start 2 $results[-1])
set --global __git_profile_comp_results $results[1..-2]
__git_profile_debug "Completions are: $__git_profile_comp_results"
__git_profile_debug "Directive is: $directive"
set -l shellCompDirectiveError 1
set -l shellCompDirectiveNoSpace 2
set -l shellCompDirectiveNoFileComp 4
set -l shellCompDirectiveFilterFileExt 8
set -l shellCompDirectiveFilterDirs 16
if test -z "$directive"
set directive 0
end
set -l compErr (math (math --scale 0 $directive / $shellCompDirectiveError) % 2)
if test $compErr -eq 1
__git_profile_debug "Received error directive: aborting."
# Might as well do file completion, in case it helps
return 1
end
set -l filefilter (math (math --scale 0 $directive / $shellCompDirectiveFilterFileExt) % 2)
set -l dirfilter (math (math --scale 0 $directive / $shellCompDirectiveFilterDirs) % 2)
if test $filefilter -eq 1; or test $dirfilter -eq 1
__git_profile_debug "File extension filtering or directory filtering not supported"
# Do full file completion instead
return 1
end
set -l nospace (math (math --scale 0 $directive / $shellCompDirectiveNoSpace) % 2)
set -l nofiles (math (math --scale 0 $directive / $shellCompDirectiveNoFileComp) % 2)
__git_profile_debug "nospace: $nospace, nofiles: $nofiles"
# If we want to prevent a space, or if file completion is NOT disabled,
# we need to count the number of valid completions.
# To do so, we will filter on prefix as the completions we have received
# may not already be filtered so as to allow fish to match on different
# criteria than the prefix.
if test $nospace -ne 0; or test $nofiles -eq 0
set -l prefix (commandline -t | string escape --style=regex)
__git_profile_debug "prefix: $prefix"
set -l completions (string match -r -- "^$prefix.*" $__git_profile_comp_results)
set --global __git_profile_comp_results $completions
__git_profile_debug "Filtered completions are: $__git_profile_comp_results"
# Important not to quote the variable for count to work
set -l numComps (count $__git_profile_comp_results)
__git_profile_debug "numComps: $numComps"
if test $numComps -eq 1; and test $nospace -ne 0
# We must first split on \t to get rid of the descriptions to be
# able to check what the actual completion will be.
# We don't need descriptions anyway since there is only a single
# real completion which the shell will expand immediately.
set -l split (string split --max 1 \t $__git_profile_comp_results[1])
# Fish won't add a space if the completion ends with any
# of the following characters: @=/:.,
set -l lastChar (string sub -s -1 -- $split)
if not string match -r -q "[@=/:.,]" -- "$lastChar"
# In other cases, to support the "nospace" directive we trick the shell
# by outputting an extra, longer completion.
__git_profile_debug "Adding second completion to perform nospace directive"
set --global __git_profile_comp_results $split[1] $split[1].
__git_profile_debug "Completions are now: $__git_profile_comp_results"
end
end
if test $numComps -eq 0; and test $nofiles -eq 0
# To be consistent with bash and zsh, we only trigger file
# completion when there are no other completions
__git_profile_debug "Requesting file completion"
return 1
end
end
return 0
end
# Since Fish completions are only loaded once the user triggers them, we trigger them ourselves
# so we can properly delete any completions provided by another script.
# Only do this if the program can be found, or else fish may print some errors; besides,
# the existing completions will only be loaded if the program can be found.
if type -q "git-profile"
# The space after the program name is essential to trigger completion for the program
# and not completion of the program name itself.
# Also, we use '> /dev/null 2>&1' since '&>' is not supported in older versions of fish.
complete --do-complete "git-profile " > /dev/null 2>&1
end
# Remove any pre-existing completions for the program since we will be handling all of them.
complete -c git-profile -e
# The call to __git_profile_prepare_completions will setup __git_profile_comp_results
# which provides the program's completion choices.
complete -c git-profile -n '__git_profile_prepare_completions' -f -a '$__git_profile_comp_results'

View File

@@ -46,7 +46,7 @@
autoStash = true autoStash = true
updateRefs = true updateRefs = true
[merge] [merge]
conflictstyle = zdiff3 conflictStyle = diff3
[pull] [pull]
rebase = true rebase = true
[color "diff-highlight"] [color "diff-highlight"]

View File

@@ -115,10 +115,10 @@ brew "harfbuzz"
brew "dependency-check" brew "dependency-check"
# Lightweight DNS forwarder and DHCP server # Lightweight DNS forwarder and DHCP server
brew "dnsmasq" brew "dnsmasq"
# .NET Core
brew "dotnet@8", link: true
# Spellchecker wrapping library # Spellchecker wrapping library
brew "enchant" brew "enchant"
# Command-line tool to interact with exercism.io
brew "exercism"
# Perl lib for reading and writing EXIF metadata # Perl lib for reading and writing EXIF metadata
brew "exiftool" brew "exiftool"
# Banner-like program prints strings as ASCII art # Banner-like program prints strings as ASCII art

View File

@@ -17,13 +17,53 @@ DEBUG="${DEBUG:-0}"
# Enable debugging with DEBUG=1 # Enable debugging with DEBUG=1
[ "${DEBUG:-0}" -eq 1 ] && set -x [ "${DEBUG:-0}" -eq 1 ] && set -x
# Detect the current shell
CURRENT_SHELL=$(ps -p $$ -ocomm= | awk -F/ '{print $NF}')
# Function to prepend a path to PATH based on the shell
x-path-prepend()
{
local dir=$1
case "$CURRENT_SHELL" in
fish)
set -U fish_user_paths "$dir" $fish_user_paths
;;
sh | bash | zsh)
PATH="$dir:$PATH"
;;
*)
echo "Unsupported shell: $CURRENT_SHELL"
exit 1
;;
esac
}
# Function to set environment variables based on the shell
x-set-env()
{
local var=$1
local value=$2
case "$CURRENT_SHELL" in
fish)
set -x "$var" "$value"
;;
sh | bash | zsh)
export "$var=$value"
;;
*)
echo "Unsupported shell: $CURRENT_SHELL"
exit 1
;;
esac
}
# Explicitly set XDG folders, if not already set # Explicitly set XDG folders, if not already set
# https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html # https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
[ -z "$XDG_CONFIG_HOME" ] && export XDG_CONFIG_HOME="$HOME/.config" x-set-env XDG_CONFIG_HOME "$HOME/.config"
[ -z "$XDG_DATA_HOME" ] && export XDG_DATA_HOME="$HOME/.local/share" x-set-env XDG_DATA_HOME "$HOME/.local/share"
[ -z "$XDG_CACHE_HOME" ] && export XDG_CACHE_HOME="$HOME/.cache" x-set-env XDG_CACHE_HOME "$HOME/.cache"
[ -z "$XDG_STATE_HOME" ] && export XDG_STATE_HOME="$HOME/.local/state" x-set-env XDG_STATE_HOME "$HOME/.local/state"
[ -z "$XDG_BIN_HOME" ] && export XDG_BIN_HOME="$HOME/.local/bin" x-set-env XDG_BIN_HOME "$HOME/.local/bin"
# Paths # Paths
x-path-prepend "/usr/local/bin" x-path-prepend "/usr/local/bin"

View File

@@ -17,7 +17,7 @@ cnoreabbrev Qall qall " quit all
"" Mappings "" Mappings
"***************************************************************************** "*****************************************************************************
noremap <C-S> :w<CR> " save buffer noremap <C-s> :w<CR> " save buffer
" Split " Split
noremap <Leader>h :<C-u>split<CR> " horizontal split noremap <Leader>h :<C-u>split<CR> " horizontal split
@@ -57,7 +57,7 @@ noremap <Leader>r :tabe <C-R>=expand("%:p:h") . "/" <CR>
" fzf.vim " fzf.vim
let $FZF_DEFAULT_COMMAND = "find * -path '*/\.*' -prune -o -path 'node_modules/**' -prune -o -path 'target/**' -prune -o -path 'vendor/**' -prune -o -path 'dist/**' -prune -o -type f -print -o -type l -print 2> /dev/null" let $FZF_DEFAULT_COMMAND = "find * -path '*/\.*' -prune -o -path 'node_modules/**' -prune -o -path 'target/**' -prune -o -path 'vendor/**' -prune -o -path 'dist/**' -prune -o -type f -print -o -type l -print 2> /dev/null"
cnoremap <C-P> <C-R>=expand("%:p:h") . "/" <CR> cnoremap <C-p> <C-R>=expand("%:p:h") . "/" <CR>
nnoremap <silent> <leader>b :Buffers<CR> nnoremap <silent> <leader>b :Buffers<CR>
nnoremap <silent> <leader>e :FZF -m<CR> nnoremap <silent> <leader>e :FZF -m<CR>
" Recovery commands from history through FZF " Recovery commands from history through FZF

View File

@@ -3,13 +3,19 @@
"metrics": false "metrics": false
}, },
"assistant": { "assistant": {
"always_allow_tool_actions": false,
"default_model": { "default_model": {
"provider": "copilot_chat", "provider": "copilot_chat",
"model": "claude-3.7-sonnet-thought" "model": "gpt-4.1"
}, },
"version": "2" "version": "2"
}, },
"languages": { "languages": {
"Python": {
"enable_language_server": true,
"allow_rewrap": "anywhere",
"auto_indent_on_paste": true
},
"Shell Script": { "Shell Script": {
"enable_language_server": true "enable_language_server": true
}, },
@@ -49,8 +55,8 @@
"vim_mode": true, "vim_mode": true,
"theme": { "theme": {
"mode": "system", "mode": "system",
"light": "Iceberg", "light": "Tokyo Night Light",
"dark": "Iceberg" "dark": "Tokyo Night Storm"
}, },
"inlay_hints": { "inlay_hints": {
"enabled": true, "enabled": true,
@@ -60,13 +66,9 @@
}, },
"ui_font_size": 16, "ui_font_size": 16,
"buffer_font_size": 16, "buffer_font_size": 16,
"buffer_font_fallbacks": [ "buffer_font_fallbacks": ["JetBrainsMono Nerd Font"],
"JetBrainsMono Nerd Font"
],
"edit_predictions": { "edit_predictions": {
"disabled_globs": [ "disabled_globs": [".env", ".env.*"]
".env"
]
}, },
"hour_format": "hour24" "hour_format": "hour24"
} }

View File

@@ -45,9 +45,9 @@
~/.config/op/plugins.sh: ~/.config/op/plugins.sh:
relink: true relink: true
path: config/op/plugins.sh path: config/op/plugins.sh
~/.config/op/plugins/*: ~/.config/op/plugins:
relink: true relink: true
path: config/op/plugins/* path: config/op/plugins
# Scripts # Scripts
~/.local/bin: ~/.local/bin:
glob: true glob: true

View File

@@ -15,9 +15,35 @@
SCRIPT=$(basename "$0") SCRIPT=$(basename "$0")
# Loads configs for better installation experience # Detect the current shell
source "$DOTFILES/config/shared.sh" CURRENT_SHELL=$(ps -p $$ -ocomm= | awk -F/ '{print $NF}')
source "${DOTFILES}/local/bin/msgr"
# Function to source files based on the shell
source_file()
{
local file=$1
case "$CURRENT_SHELL" in
fish)
if [[ -f "$file.fish" ]]; then
source "$file.fish"
else
echo "Fish shell file not found: $file.fish"
exit 1
fi
;;
sh | bash | zsh)
source "$file"
;;
*)
echo "Unsupported shell: $CURRENT_SHELL"
exit 1
;;
esac
}
# Modify the source commands to use the new function
source_file "$DOTFILES/config/shared.sh"
source_file "${DOTFILES}/local/bin/msgr"
# Menu builder # Menu builder
menu_builder() menu_builder()
@@ -267,7 +293,7 @@ section_helpers()
{ {
USAGE_PREFIX="$SCRIPT helpers <command>" USAGE_PREFIX="$SCRIPT helpers <command>"
MENU=( MENU=(
"aliases:<shell> (bash, zsh) Show aliases" "aliases:<shell> (bash, zsh, fish) Show aliases"
"colors:Show colors" "colors:Show colors"
"env:Show environment variables" "env:Show environment variables"
"functions:Show functions" "functions:Show functions"
@@ -297,8 +323,11 @@ section_helpers()
"bash") "bash")
bash -ixc : 2>&1 | grep -E '> alias' | sed "s|$HOME|~|" | grep -v "(eval)" bash -ixc : 2>&1 | grep -E '> alias' | sed "s|$HOME|~|" | grep -v "(eval)"
;; ;;
"fish")
fish -ic "alias" | sed "s|$HOME|~|"
;;
*) *)
echo "$SCRIPT helpers aliases <shell> (bash, zsh)" echo "$SCRIPT helpers aliases <shell> (bash, zsh, fish)"
;; ;;
esac esac
;; ;;

View File

@@ -11,10 +11,10 @@ set -euo pipefail
VERBOSE=0 VERBOSE=0
CHECK_PATTERN="text: auto" CHECK_PATTERN="text: auto"
EXIT_ON_MISSING=0 EXIT_ON_MISSING=0
SUGGEST_RULES=1 # Suggestions enabled by default SUGGEST_RULES=1 # Suggestions enabled by default
WRITE_RULES=0 # Writing to file is opt-in WRITE_RULES=0 # Writing to file is opt-in
FORMAT_WIDTH=0 # Auto-width by default (0 means auto) FORMAT_WIDTH=0 # Auto-width by default (0 means auto)
MIN_FORMAT_WIDTH=20 # Minimum format width MIN_FORMAT_WIDTH=20 # Minimum format width
DEBUG="${DEBUG:-0}" DEBUG="${DEBUG:-0}"
@@ -23,27 +23,33 @@ if [ "$DEBUG" -eq 1 ]; then
fi fi
# Output functions # Output functions
msg_err() { msg_err()
{
echo -e "\e[31m$@\e[0m" >&2 echo -e "\e[31m$@\e[0m" >&2
} }
msg_success() { msg_success()
{
echo -e "\e[32m$@\e[0m" echo -e "\e[32m$@\e[0m"
} }
msg_warn() { msg_warn()
{
echo -e "\e[33m$@\e[0m" >&2 echo -e "\e[33m$@\e[0m" >&2
} }
msg_info() { msg_info()
{
echo -e "\e[36m$@\e[0m" echo -e "\e[36m$@\e[0m"
} }
msg_debug() { msg_debug()
{
[[ $VERBOSE -eq 1 ]] && echo -e "\e[35m$@\e[0m" [[ $VERBOSE -eq 1 ]] && echo -e "\e[35m$@\e[0m"
} }
show_help() { show_help()
{
cat << EOF cat << EOF
Usage: $(basename "$0") [OPTIONS] Usage: $(basename "$0") [OPTIONS]
@@ -64,31 +70,31 @@ EOF
# Parse arguments # Parse arguments
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
-h|--help) -h | --help)
show_help show_help
exit 0 exit 0
;; ;;
-v|--verbose) -v | --verbose)
VERBOSE=1 VERBOSE=1
shift shift
;; ;;
-e|--exit) -e | --exit)
EXIT_ON_MISSING=1 EXIT_ON_MISSING=1
shift shift
;; ;;
-p|--pattern) -p | --pattern)
CHECK_PATTERN="$2" CHECK_PATTERN="$2"
shift 2 shift 2
;; ;;
-n|--no-suggest) -n | --no-suggest)
SUGGEST_RULES=0 SUGGEST_RULES=0
shift shift
;; ;;
-w|--write) -w | --write)
WRITE_RULES=1 WRITE_RULES=1
shift shift
;; ;;
-f|--format-width) -f | --format-width)
if [[ $2 =~ ^[0-9]+$ ]]; then if [[ $2 =~ ^[0-9]+$ ]]; then
FORMAT_WIDTH=$2 FORMAT_WIDTH=$2
shift 2 shift 2
@@ -106,7 +112,8 @@ while [[ $# -gt 0 ]]; do
done done
# Function to check if git is installed # Function to check if git is installed
check_git_installed() { check_git_installed()
{
if ! command -v git &> /dev/null; then if ! command -v git &> /dev/null; then
msg_err "git could not be found, please install it first" msg_err "git could not be found, please install it first"
exit 1 exit 1
@@ -114,15 +121,17 @@ check_git_installed() {
} }
# Check if we're in a git repository # Check if we're in a git repository
check_git_repo() { check_git_repo()
if ! git rev-parse --is-inside-work-tree &>/dev/null; then {
if ! git rev-parse --is-inside-work-tree &> /dev/null; then
msg_err "Not inside a git repository" msg_err "Not inside a git repository"
exit 1 exit 1
fi fi
} }
# Check if we're in the git root directory # Check if we're in the git root directory
check_git_root() { check_git_root()
{
local git_root local git_root
git_root=$(git rev-parse --show-toplevel) git_root=$(git rev-parse --show-toplevel)
local current_dir local current_dir
@@ -136,7 +145,8 @@ check_git_root() {
} }
# Check if .gitattributes exists # Check if .gitattributes exists
check_gitattributes_exists() { check_gitattributes_exists()
{
if [[ ! -f ".gitattributes" ]]; then if [[ ! -f ".gitattributes" ]]; then
msg_err ".gitattributes file not found in the repository root" msg_err ".gitattributes file not found in the repository root"
msg_warn "Create a .gitattributes file before running this command" msg_warn "Create a .gitattributes file before running this command"
@@ -145,7 +155,8 @@ check_gitattributes_exists() {
} }
# Format rule with proper alignment # Format rule with proper alignment
format_rule() { format_rule()
{
local pattern="$1" local pattern="$1"
local attributes="$2" local attributes="$2"
local width="$3" local width="$3"
@@ -166,7 +177,8 @@ format_rule() {
} }
# Get the file extension properly, handling special cases # Get the file extension properly, handling special cases
get_file_extension() { get_file_extension()
{
local file="$1" local file="$1"
local basename=$(basename "$file") local basename=$(basename "$file")
local extension="" local extension=""
@@ -199,7 +211,8 @@ get_file_extension() {
} }
# Suggest appropriate gitattributes rules based on file extension # Suggest appropriate gitattributes rules based on file extension
suggest_rule() { suggest_rule()
{
local file="$1" local file="$1"
local extension="" local extension=""
local pattern="" local pattern=""
@@ -237,15 +250,15 @@ suggest_rule() {
# Common text files # Common text files
case "$extension" in case "$extension" in
# Shell scripts # Shell scripts
sh|bash|zsh|fish) sh | bash | zsh | fish)
attributes="text eol=lf diff=shell" attributes="text eol=lf diff=shell"
;; ;;
# Web development # Web development
html|htm|xhtml|css|scss|sass|less) html | htm | xhtml | css | scss | sass | less)
attributes="text eol=lf diff=html" attributes="text eol=lf diff=html"
;; ;;
js|jsx|ts|tsx|json|json5) js | jsx | ts | tsx | json | json5)
attributes="text eol=lf diff=javascript" attributes="text eol=lf diff=javascript"
;; ;;
@@ -262,20 +275,20 @@ suggest_rule() {
go) go)
attributes="text eol=lf diff=golang" attributes="text eol=lf diff=golang"
;; ;;
java|kt|scala) java | kt | scala)
attributes="text eol=lf diff=java" attributes="text eol=lf diff=java"
;; ;;
c|cpp|h|hpp) c | cpp | h | hpp)
attributes="text eol=lf diff=cpp" attributes="text eol=lf diff=cpp"
;; ;;
# Documentation # Documentation
md|markdown|txt) md | markdown | txt)
attributes="text eol=lf" attributes="text eol=lf"
;; ;;
# Configuration files # Configuration files
yml|yaml|toml|ini|cfg|conf) yml | yaml | toml | ini | cfg | conf)
attributes="text eol=lf" attributes="text eol=lf"
;; ;;
@@ -283,24 +296,24 @@ suggest_rule() {
git) git)
attributes="text eol=lf" attributes="text eol=lf"
;; ;;
gitignore|gitattributes) gitignore | gitattributes)
attributes="text eol=lf" attributes="text eol=lf"
;; ;;
# Binary files # Binary files
png|jpg|jpeg|gif|ico|svg|webp|avif) png | jpg | jpeg | gif | ico | svg | webp | avif)
attributes="binary" attributes="binary"
;; ;;
pdf|doc|docx|xls|xlsx|ppt|pptx) pdf | doc | docx | xls | xlsx | ppt | pptx)
attributes="binary" attributes="binary"
;; ;;
zip|tar|gz|7z|rar) zip | tar | gz | 7z | rar)
attributes="binary" attributes="binary"
;; ;;
mp3|mp4|avi|mov|wav|ogg) mp3 | mp4 | avi | mov | wav | ogg)
attributes="binary" attributes="binary"
;; ;;
ttf|otf|woff|woff2|eot) ttf | otf | woff | woff2 | eot)
attributes="binary" attributes="binary"
;; ;;
@@ -321,7 +334,8 @@ suggest_rule() {
} }
# Function to check for missing .gitattributes # Function to check for missing .gitattributes
check_gitattributes() { check_gitattributes()
{
local missing_attributes local missing_attributes
msg_info "Checking for pattern: $CHECK_PATTERN" msg_info "Checking for pattern: $CHECK_PATTERN"
@@ -362,7 +376,8 @@ check_gitattributes() {
} }
# Parse rule string and extract pattern and attributes # Parse rule string and extract pattern and attributes
parse_rule() { parse_rule()
{
local rule="$1" local rule="$1"
if [[ "$rule" == "#"* ]]; then if [[ "$rule" == "#"* ]]; then
@@ -379,7 +394,8 @@ parse_rule() {
} }
# Check shell scripts by name regardless of extension # Check shell scripts by name regardless of extension
detect_shell_scripts() { detect_shell_scripts()
{
msg_debug "Detecting shell scripts by name regardless of extension..." msg_debug "Detecting shell scripts by name regardless of extension..."
local shell_scripts_rules="" local shell_scripts_rules=""
@@ -510,13 +526,14 @@ detect_shell_scripts() {
# Return the formatted arrays # Return the formatted arrays
local rules_count=${#patterns[@]} local rules_count=${#patterns[@]}
for ((i=0; i<rules_count; i++)); do for ((i = 0; i < rules_count; i++)); do
echo "${patterns[$i]}:${attributes[$i]}" echo "${patterns[$i]}:${attributes[$i]}"
done done
} }
# Function to suggest gitattributes rules # Function to suggest gitattributes rules
suggest_gitattributes() { suggest_gitattributes()
{
local missing_attributes="$1" local missing_attributes="$1"
local files local files
local extension_suggestions=() local extension_suggestions=()
@@ -613,7 +630,7 @@ suggest_gitattributes() {
# Format and output all suggestions with proper alignment # Format and output all suggestions with proper alignment
local rule_count=${#all_patterns[@]} local rule_count=${#all_patterns[@]}
for ((i=0; i<rule_count; i++)); do for ((i = 0; i < rule_count; i++)); do
local pattern="${all_patterns[$i]}" local pattern="${all_patterns[$i]}"
local attributes="${all_attributes[$i]}" local attributes="${all_attributes[$i]}"
@@ -637,7 +654,8 @@ suggest_gitattributes() {
} }
# Write suggestions to .gitattributes file # Write suggestions to .gitattributes file
write_to_gitattributes() { write_to_gitattributes()
{
local suggestions="$1" local suggestions="$1"
local gitattributes=".gitattributes" local gitattributes=".gitattributes"
@@ -664,7 +682,8 @@ write_to_gitattributes() {
} }
# Main function # Main function
main() { main()
{
check_git_installed check_git_installed
check_git_repo check_git_repo
check_git_root check_git_root

View File

@@ -201,7 +201,7 @@ process_args()
# Initialize log file if specified # Initialize log file if specified
if [[ -n "$LOG_FILE" ]]; then if [[ -n "$LOG_FILE" ]]; then
# Create log directory if it doesn't exist # Create log directory if it doesn't exist
mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null || true mkdir -p "$(dirname "$LOG_FILE")" 2> /dev/null || true
# Initialize log file # Initialize log file
echo "[$(date +"%Y-%m-%d %H:%M:%S")] [INFO] Started git-update-dirs version $VERSION" > "$LOG_FILE" echo "[$(date +"%Y-%m-%d %H:%M:%S")] [INFO] Started git-update-dirs version $VERSION" > "$LOG_FILE"
fi fi
@@ -375,7 +375,7 @@ cleanup_branches()
local cleaned=0 local cleaned=0
local current_branch output local current_branch output
current_branch=$(git symbolic-ref --short HEAD 2>/dev/null) current_branch=$(git symbolic-ref --short HEAD 2> /dev/null)
# Skip branch cleanup if we're not on a main branch # Skip branch cleanup if we're not on a main branch
if [[ ! "$current_branch" =~ ^(master|main|develop)$ ]]; then if [[ ! "$current_branch" =~ ^(master|main|develop)$ ]]; then
@@ -397,7 +397,7 @@ cleanup_branches()
# Delete branches # Delete branches
for branch in $output; do for branch in $output; do
if [[ -n "$branch" ]]; then if [[ -n "$branch" ]]; then
if git branch -d "$branch" &>/dev/null; then if git branch -d "$branch" &> /dev/null; then
((cleaned++)) ((cleaned++))
log "INFO" "Deleted merged branch $branch in $(pwd)" log "INFO" "Deleted merged branch $branch in $(pwd)"
else else
@@ -426,7 +426,7 @@ update_repo()
# Show progress before starting the operation # Show progress before starting the operation
show_progress "$PROCESSED" "$TOTAL" "${dir%/}" show_progress "$PROCESSED" "$TOTAL" "${dir%/}"
cd "$dir" 2>/dev/null || { cd "$dir" 2> /dev/null || {
log "ERROR" "Could not enter directory $dir" log "ERROR" "Could not enter directory $dir"
echo -e "\n${RED}Error: Could not enter directory $dir${NC}" >&2 echo -e "\n${RED}Error: Could not enter directory $dir${NC}" >&2
((FAILED++)) ((FAILED++))
@@ -438,37 +438,37 @@ update_repo()
log "INFO" "Skipping directory with no remotes: $dir" log "INFO" "Skipping directory with no remotes: $dir"
msg "Skipping directory with no remotes: $dir" msg "Skipping directory with no remotes: $dir"
((SKIPPED++)) ((SKIPPED++))
cd - >/dev/null || true cd - > /dev/null || true
return 1 return 1
fi fi
# Get current branch name # Get current branch name
current_branch=$(git symbolic-ref --short HEAD 2>/dev/null) current_branch=$(git symbolic-ref --short HEAD 2> /dev/null)
if [[ -z "$current_branch" ]]; then if [[ -z "$current_branch" ]]; then
log "INFO" "Skipping repository in detached HEAD state: $dir" log "INFO" "Skipping repository in detached HEAD state: $dir"
msg "Skipping repository in detached HEAD state: $dir" msg "Skipping repository in detached HEAD state: $dir"
((SKIPPED++)) ((SKIPPED++))
cd - >/dev/null || true cd - > /dev/null || true
return 1 return 1
fi fi
# Check if current branch has tracking information # Check if current branch has tracking information
eval "git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null" &>/dev/null || { eval "git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null" &> /dev/null || {
log "INFO" "Skipping branch '$current_branch' without tracking info in $dir" log "INFO" "Skipping branch '$current_branch' without tracking info in $dir"
msg "Skipping branch '$current_branch' without tracking info in $dir" msg "Skipping branch '$current_branch' without tracking info in $dir"
((SKIPPED++)) ((SKIPPED++))
cd - >/dev/null || true cd - > /dev/null || true
return 1 return 1
} }
# Check if remote is accessible # Check if remote is accessible
remote_name=$(git config --get branch."$current_branch".remote) remote_name=$(git config --get branch."$current_branch".remote)
if [[ -n "$remote_name" ]]; then if [[ -n "$remote_name" ]]; then
if ! git ls-remote --exit-code "$remote_name" &>/dev/null; then if ! git ls-remote --exit-code "$remote_name" &> /dev/null; then
log "WARNING" "Skipping repository with inaccessible remote '$remote_name': $dir" log "WARNING" "Skipping repository with inaccessible remote '$remote_name': $dir"
msg "Skipping repository with inaccessible remote: $dir" msg "Skipping repository with inaccessible remote: $dir"
((SKIPPED++)) ((SKIPPED++))
cd - >/dev/null || true cd - > /dev/null || true
return 1 return 1
fi fi
fi fi
@@ -478,7 +478,7 @@ update_repo()
log "WARNING" "Skipping repository with unmerged files: $dir" log "WARNING" "Skipping repository with unmerged files: $dir"
msg "Skipping repository with unmerged files: $dir" msg "Skipping repository with unmerged files: $dir"
((UNMERGED++)) ((UNMERGED++))
cd - >/dev/null || true cd - > /dev/null || true
return 1 return 1
fi fi
@@ -579,7 +579,7 @@ update_repo()
fi fi
# Return to original directory # Return to original directory
cd - >/dev/null || true cd - > /dev/null || true
# Show progress after completion # Show progress after completion
show_progress "$PROCESSED" "$TOTAL" "${dir%/} - Done" show_progress "$PROCESSED" "$TOTAL" "${dir%/} - Done"

View File

@@ -1,209 +0,0 @@
#!/usr/bin/env perl
=head1 NAME
dupes - Report on files with duplicate contents, via SHA1 hash.
=cut
=head1 SYNOPSIS
dupes [options] directory
General Options:
--help Show the help information for this script.
--verbose Show useful debugging information.
=cut
=head1 ABOUT
dupes is a simple script to report upon files that are identical,
recursively.
The process involves calculating the SHA1 hash of the file contents
and reporting on anything collisions we see.
Note that a collision might be caused by a symbolic link, or hardlink,
so blindly deleting duplicates without investigation is almost certainly
a mistake.
=cut
=head1 AUTHOR
Steve
--
http://www.steve.org.uk/
=cut
=head1 LICENSE
Copyright (c) 2013 by Steve Kemp. All rights reserved.
This script is free software;you can redistribute it and/or modify it under
the same terms as Perl itself.
The LICENSE file contains the full text of the license.
=cut
use strict;
use warnings;
use File::Find;
use Getopt::Long;
use Pod::Usage;
#
# Parse the arguments
#
my %config = parsedOptions();
#
# The path to examine.
#
my $path = $ARGV[0] || '.';
#
# Get the hashing object, dynamically.
#
my $ctx = getHashObject();
my %digest;
#
# Find files and store the hash of their contents.
#
find( {
'wanted' => sub {
if ( -f $_ )
{
lstat;
if ( ( -r _ ) && ( !-l _ ) )
{
$ctx->reset;
$ctx->addfile($_);
my $md5 = $ctx->hexdigest;
if ( exists $digest{ $md5 } )
{
push @{ $digest{ $md5 }->{ 'dupes' } }, $_;
}
else
{
$digest{ $md5 } = { 'file' => $_,
'dupes' => [] };
}
}
}
else
{
$config{ 'verbose' } && print "Entering $_\n";
}
},
'no_chdir' => 1
},
$path
);
#
# Report upon collisions.
#
foreach my $hash ( keys %digest )
{
my $dupes = $digest{ $hash }->{ 'dupes' };
my $src = $digest{ $hash }->{ 'file' };
if (@$dupes)
{
print $src . "\n";
foreach my $dupe (@$dupes)
{
print "\t$dupe\n";
}
}
}
#
# All done.
#
exit(0);
=begin doc
Load one of M<Digest::SHA> and M<Digest::SHA1>, depending on what is available.
=end doc
=cut
sub getHashObject
{
my $hash = undef;
foreach my $module (qw! Digest::SHA Digest::SHA1 !)
{
# If we succeeded in calculating the hash we're done.
next if ( defined($hash) );
# Attempt to load the module
my $eval = "use $module;";
## no critic (Eval)
eval($eval);
## use critic
if ( !$@ )
{
$hash = $module->new;
}
}
if ($hash)
{
return ($hash);
}
else
{
print "Failed to load either DIgest::SHA or Digest::SHA1\n";
exit(1);
}
}
=begin doc
Parse the options and return suitable values.
=end doc
=cut
sub parsedOptions
{
my %vars;
exit
if (
!GetOptions( "help" => \$vars{ 'help' },
"verbose" => \$vars{ 'verbose' } ) );
pod2usage(1) if ( $vars{ 'help' } );
return (%vars);
}