Skip to content

Commit

Permalink
Merge pull request #1 from jmylchreest/main
Browse files Browse the repository at this point in the history
Support for multiple paths
  • Loading branch information
baliestri authored Feb 9, 2025
2 parents ef116cc + 87a5305 commit 1e29659
Show file tree
Hide file tree
Showing 2 changed files with 207 additions and 51 deletions.
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ zgenom load empresslabs/gitprofiles.plugin.zsh
zplug empresslabs/gitprofiles.plugin.zsh
```

#### [antidote](https://github.com/mattmc3/antidote.git)

Add the following to your .zsh_plugins.txt file for antidote:

```shell
empresslabs/gitprofiles.plugin.zsh
```

## Usage

#### Define where your profiles are stored
Expand All @@ -73,5 +81,17 @@ zstyle ":empresslabs:git:profile" path "$HOME/.config/git/profiles"
name = Bruno Sales
email = [email protected]
# signingkey = 1234567890
path = "/home/baliestri/work"
paths = "/home/baliestri/work"

[profile "personal"]
name = Bruno Sales
email = [email protected]
# signingkey = 1234567890
paths = "~/personal",
"~/src/personal/*",
"~/src/mytopsecretproject"
```

Multiple paths can be defined for a profile, separated by either newline or commas. The paths are processed in the order they are defined, with exact matches taking precedence over wild card matches. Tildes are expanded to ${HOME}.

It is possible to get debug information by setting the `GP_DEBUG` environment variable to any value within your current session.
236 changes: 186 additions & 50 deletions gitprofiles.plugin.zsh
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
# Copyright (c) Bruno Sales <[email protected]>. Licensed under the MIT License.
# See the LICENSE file in the project root for full license information.

# vim: set ts=4 sw=4 tw=0 et :
#!/usr/bin/env zsh

function __is_git_repo() {
# if git isnt installes, this will also return false
if [[ -n "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]]; then
return 0
else
return 1
fi
}

function __gitprofiles_hook() {
## Check if git is installed
if (( ! $+commands[git] )); then
# make sure we're in a git repo
if ! __is_git_repo; then
return 1
fi

local -A profile_path_map=()
local -A profile_cfg_map=()
typeset -A profile_paths_map
typeset -A profile_cfg_map

## Get the path to the profile file
zstyle -s ":empresslabs:git:profile" path profile_filepath
Expand All @@ -20,70 +30,196 @@ function __gitprofiles_hook() {
return 1
fi

# Ensure glob patterns that don't match don't cause errors
setopt LOCAL_OPTIONS NO_NOMATCH

## Load all stored profiles
local profiles=($(grep -o '\[profile [^]]*\]' ${profile_filepath} | tr -d '[]" ' | sed 's/profile//g' | tr '\n' ' '))
local profiles=()
local current_section=""

while IFS= read -r line; do
# Skip empty lines and comments
[[ -z "$line" || "$line" == \#* ]] && continue

# Check for profile section
if [[ "$line" =~ '^\[profile[[:space:]]+"([^"]+)"\]' ]]; then
current_section="${match[1]}"
profiles+=("$current_section")
# [[ -n "${GP_DEBUG}" ]] && print -u2 "Found profile: ${current_section}"
fi
done < "${profile_filepath}"

## Check if default profile exists
if [[ ! "${profiles}" =~ "default" ]]; then
echo "gitprofiles: 'default' profile not found in '${profile_filepath}'"
if [[ ! "${profiles[(r)default]}" ]]; then
print -u2 "gitprofiles: 'default' profile not found in '${profile_filepath}'"
return 1
fi

## Iterate over all profiles to get the name, email, signingkey and path
# Function to parse paths into an array and support tilde expansion
function __parse_paths() {
local raw_paths="$1"
# [[ -n "${GP_DEBUG}" ]] && print -u2 "Raw paths input: ${(q)raw_paths}"

local -a path_lines
# split on commas or newlines
path_lines=(${(s:,:)${(f)raw_paths}})

# Process each line
local -a paths
local line
for line in $path_lines; do
# remove newlines
line="${line//'\n'/}"

# remove quotes
line="${line//[\"\']}"

# Remove trailing commas
line="${line%%,}"

# Trim whitespace
line="${line## }"
line="${line%% }"

# Expand tildes
if [[ $line = "~"* ]]; then
line=${HOME}${line#"~"}
fi

# Skip empty lines
[[ -n "$line" ]] && paths+=("$line")
done

# Expand tildes
# paths=(${~paths}) # this doesnt work as it expands the glob

# [[ -n "${GP_DEBUG}" ]] && print -u2 "Final paths: ${paths}"
print -l -- ${paths}
}

## Parse configuration for each profile
for profile in ${profiles}; do
local -A profile_value_map=()

while read -r key value; do
case "${key}" in
name)
profile_value_map[name]="${value}"
;;
email)
profile_value_map[email]="${value}"
;;
signingkey)
profile_value_map[signingkey]="${value}"
;;
path)
profile_value_map[path]="${value}"
;;
esac
done < <(awk -F ' = ' '/^\[profile/{p=0} /^\[profile "[^"]*'"${profile}"'"/{p=1} p {gsub(/"/, "", $2); print $1,$2}' ${profile_filepath})

profile_path_map[${profile}]="${profile_value_map[path]}"

profile_cfg_map[${profile}.name]="${profile_value_map[name]}"
profile_cfg_map[${profile}.email]="${profile_value_map[email]}"

if [[ -n "${profile[signingkey]}" ]]; then
profile_cfg_map[${profile}.signingkey]="${profile_value_map[signingkey]}"
typeset -A profile_value_map
local in_current_profile=0
local in_paths=0
local paths_tmp=()

while IFS= read -r line; do
# Skip empty lines and comments
[[ -z "$line" || "$line" == \#* ]] && continue

# Check for profile section
if [[ "$line" =~ '^\[profile[[:space:]]+"([^"]+)"\]' ]]; then
if [[ "${match[1]}" == "$profile" ]]; then
in_current_profile=1
else
in_current_profile=0
in_paths=0
fi
continue
fi

# Only process lines for current profile
(( in_current_profile )) || continue

# Parse key-value pairs
if [[ "$line" =~ '^[[:space:]]*([^=]+)[[:space:]]*=[[:space:]]*(.*)' ]]; then
local key="${match[1]## }" # Trim leading spaces
key="${key%% }" # Trim trailing spaces
local value="${match[2]}" # Keep spaces in value for now

case "$key" in
name|email|signingkey)
# Remove quotes and trim for non-path values
value="${value## }" # Trim leading spaces
value="${value%% }" # Trim trailing spaces
value="${value#[\"\']}" # Remove leading quote
value="${value%[\"\']}" # Remove trailing quote
profile_value_map[$key]="$value"
;;
path|paths)
in_paths=1
paths_tmp=("$value")
;;
esac
elif (( in_paths )) && [[ "$line" =~ '^[[:space:]]+(.*)' ]]; then
# Handle indented continuation lines for paths
local value="${match[1]}"
paths_tmp+=("$value")
fi
done < "${profile_filepath}"

# Join and parse paths
if (( ${#paths_tmp} > 0 )); then
local joined_paths="${(j:\n:)paths_tmp}"
profile_paths_map[$profile]="${(@f)$(__parse_paths "$joined_paths")}"
fi
done

## Get the current directory
local -A current=()
current[dir]=$(pwd)
# Store other configurations
profile_cfg_map[$profile.name]="${profile_value_map[name]}"
profile_cfg_map[$profile.email]="${profile_value_map[email]}"

## Check if the current directory is in one of the profiles paths
for profile in ${(k)profile_path_map}; do
if [[ "${current[dir]}" =~ "${profile_path_map[${profile}]}" ]]; then
local current[profile]="${profile}"
break
if [[ -n "${profile_value_map[signingkey]}" ]]; then
profile_cfg_map[$profile.signingkey]="${profile_value_map[signingkey]}"
fi
done

## If the current directory is not in any profile path, use the default profile
if [[ -z "${current[profile]}" ]]; then
local current[profile]="default"
# Get current directory
local current_dir=$(pwd)
local matched_profile="default"

[[ -n "${GP_DEBUG}" ]] && print -u2 "Current directory: ${current_dir}"

# First pass: Check for exact matches
for profile in ${(k)profile_paths_map}; do
[[ -n "${GP_DEBUG}" ]] && print -u2 "Testing Profile (exact): ${profile}"

local paths=(${=profile_paths_map[$profile]}) # Convert to array
for path_pattern in $paths; do
# Only do exact match if pattern doesn't contain a wildcard
if [[ ! $path_pattern =~ '[*?]' ]] && [[ "${current_dir}" = "${path_pattern}" ]]; then
[[ -n "${GP_DEBUG}" ]] && print -u2 "Matched (exact) path: ${path_pattern}"
matched_profile="${profile}"
break 2
fi
done
done

# Second pass: Check for wildcard matches (only if no exact match found)
if [[ "${matched_profile}" = "default" ]]; then
for profile in ${(k)profile_paths_map}; do
[[ -n "${GP_DEBUG}" ]] && print -u2 "Testing Profile (wildcard): ${profile}"

local paths=(${=profile_paths_map[$profile]}) # Convert to array
for path_pattern in $paths; do
# Only do regex match if we have a wildcard
if [[ $path_pattern =~ '[*?]' ]] && [[ "${current_dir}" =~ "${path_pattern}" ]]; then
[[ -n "${GP_DEBUG}" ]] && print -u2 "Matched (*) path: ${path_pattern}"
matched_profile="${profile}"
break 2
fi
done
done
fi

## Set the current profile name and email
git config --global user.name "${profile_cfg_map[${current[profile]}.name]}"
git config --global user.email "${profile_cfg_map[${current[profile]}.email]}"
git config --global user.name "${profile_cfg_map[${matched_profile}.name]}"
git config --global user.email "${profile_cfg_map[${matched_profile}.email]}"

## Set the current profile signingkey if it exists
if [[ -n "${profile_cfg_map[${current[profile]}.signingkey]}" ]]; then
git config --global user.signingkey "${profile_cfg_map[${current[profile]}.signingkey]}"
if [[ -n "${profile_cfg_map[${matched_profile}.signingkey]}" ]]; then
git config --global user.signingkey "${profile_cfg_map[${matched_profile}.signingkey]}"
fi

# Print debug information if GP_DEBUG is set
if [[ -n "${GP_DEBUG}" ]] && [[ -n "${matched_profile}" ]]; then
print -u2 "Matched profile: ${matched_profile}"
print -u2 "Using configuration:"
print -u2 " name: ${profile_cfg_map[${matched_profile}.name]}"
print -u2 " email: ${profile_cfg_map[${matched_profile}.email]}"
if [[ -n "${profile_cfg_map[${matched_profile}.signingkey]}" ]]; then
print -u2 " signingkey: ${profile_cfg_map[${matched_profile}.signingkey]}"
fi
fi
}

Expand Down

0 comments on commit 1e29659

Please sign in to comment.