#!/usr/bin/env bash

# shellcheck disable=2034
GITSECRET_VERSION='0.2.3'
#!/usr/bin/env bash


# shellcheck disable=1117
function __replace_in_file_linux {
  sed -i.bak "s/^\($1\s*=\s*\).*\$/\1$2/" "$3"
}


function __temp_file_linux {
  local filename
  filename=$(mktemp)
  echo "$filename"
}
#!/usr/bin/env bash


# shellcheck disable=1117
function __replace_in_file_osx {
  sed -i.bak "s/^\($1[[:space:]]*=[[:space:]]*\).*\$/\1$2/" "$3"
}


function __temp_file_osx {
  : "${TMPDIR:=/tmp}"
  local filename
  filename=$(mktemp -t _gitsecrets_XXX )
  echo "$filename";
}
#!/usr/bin/env bash

# Folders:
_SECRETS_DIR=".gitsecret"
_SECRETS_DIR_KEYS="${_SECRETS_DIR}/keys"
_SECRETS_DIR_PATHS="${_SECRETS_DIR}/paths"

# Files:
_SECRETS_DIR_KEYS_MAPPING="${_SECRETS_DIR_KEYS}/mapping.cfg"
_SECRETS_DIR_KEYS_TRUSTDB="${_SECRETS_DIR_KEYS}/trustdb.gpg"

_SECRETS_DIR_PATHS_MAPPING="${_SECRETS_DIR_PATHS}/mapping.cfg"

: "${SECRETS_EXTENSION:=".secret"}"

# Commands:
: "${SECRETS_GPG_COMMAND:="gpg"}"
: "${SECRETS_CHECKSUM_COMMAND:="sha256sum"}"


# AWK scripts:
# shellcheck disable=2016
AWK_FSDB_HAS_RECORD='
BEGIN { FS=":"; OFS=":"; cnt=0; }
{
  if ( key == $1 )
  {
    cnt++
  }
}
END { if ( cnt > 0 ) print "0"; else print "1"; }
'

# shellcheck disable=2016
AWK_FSDB_RM_RECORD='
BEGIN { FS=":"; OFS=":"; }
{
  if ( key != $1 )
  {
    print $1,$2;
  }
}
'

# shellcheck disable=2016
AWK_FSDB_CLEAR_HASHES='
BEGIN { FS=":"; OFS=":"; }
{
  print $1,"";
}
'

# shellcheck disable=2016
AWK_GPG_VER_CHECK='
/^gpg/{
  version=$3
  n=split(version,array,".")
  if( n >= 2) {
    if(array[1] >= 2)
    {
      if(array[2] >= 1)
      {
        print 1
      }
      else
      {
        print 0
      }
    }
    else
    {
      print 0
    }
  }
  else if(array[1] >= 2)
  {
    print 1
  }
  else
  {
    print 0
  }
}
'

# This is 1 for gpg vesion  2.1 or greater, otherwise 0
GPG_VER_21="$(gpg --version | gawk "$AWK_GPG_VER_CHECK")"


# Bash:

function _function_exists {
  local function_name="$1" # required

  declare -f -F "$function_name" > /dev/null 2>&1
  echo $?
}


# OS based:

function _os_based {
  # Pass function name as first parameter.
  # It will be invoked as os-based function with the postfix.

  case "$(uname -s)" in

    Darwin)
      "$1_osx" "${@:2}"
    ;;

    Linux)
      "$1_linux" "${@:2}"
    ;;

    # TODO: add MS Windows support.
    # CYGWIN*|MINGW32*|MSYS*)
    #   $1_ms ${@:2}
    # ;;

    *)
      _abort 'unsupported OS.'
    ;;
  esac
}


# File System:

function _set_config {
  # This function creates a line in the config, or alters it.

  local key="$1" # required
  local value="$2" # required
  local filename="$3" # required

  # The exit status is 0 (true) if the name was found, 1 (false) if not:
  local contains
  contains=$(grep -Fq "$key" "$filename"; echo "$?")

  # Append or alter?
  if [[ "$contains" -eq 0 ]]; then
    _os_based __replace_in_file "$@"
  elif [[ "$contains" -eq 1 ]]; then
    echo "${key} = ${value}" >> "$filename"
  fi
}


function _file_has_line {
  # First parameter is the key, second is the filename.

  local key="$1" # required
  local filename="$2" # required

  local contains
  contains=$(grep -qw "$key" "$filename"; echo $?)

  # 0 on contains, 1 for error.
  echo "$contains"
}


function _delete_line {
  local escaped_path
  # shellcheck disable=2001
  escaped_path=$(echo "$1" | sed -e 's/[\/&]/\\&/g') # required

  local line="$2" # required

  sed -i.bak "/$escaped_path/d" "$line"
}


function _temporary_file {
  # This function creates temporary file
  # which will be removed on system exit.
  filename=$(_os_based __temp_file)  # is not `local` on purpose.

  trap 'echo "cleaning up..."; rm -f "$filename";' EXIT
}


function _unique_filename {
  # First parameter is base-path, second is filename,
  # third is optional extension.
  local n=0
  local base_path="$1"
  local result="$2"

  while true; do
    if [[ ! -f "$base_path/$result" ]]; then
      break
    fi

    n=$(( n + 1 ))
    result="${2}-${n}" # calling to the original "$2"
  done
  echo "$result"
}

# Helper function


function _gawk_inplace {
  local parms="$*"
  local dest_file
  dest_file="$(echo "$parms" | gawk -v RS="'" -v FS="'" 'END{ gsub(/^\s+/,""); print $1 }')"

  _temporary_file

  bash -c "gawk ${parms}" > "$filename"
  mv "$filename" "$dest_file"
}


# File System Database (fsdb):


function _get_record_filename {
  # Returns 1st field from passed record
  local record="$1"
  local filename
  filename=$(echo "$record" | awk -F: '{print $1}')

  echo "$filename"
}


function _get_record_hash {
  # Returns 2nd field from passed record
  local record="$1"
  local hash
  hash=$(echo "$record" | awk -F: '{print $2}')

  echo "$hash"
}


function _fsdb_has_record {
  # First parameter is the key
  # Second is the fsdb
  local key="$1"  # required
  local fsdb="$2" # required

  # 0 on contains, 1 for error.
  gawk -v key="$key" "$AWK_FSDB_HAS_RECORD" "$fsdb"
}


function _fsdb_rm_record {
  # First parameter is the key (filename)
  # Second is the path to fsdb
  local key="$1"  # required
  local fsdb="$2" # required

  _gawk_inplace -v key="$key" "'$AWK_FSDB_RM_RECORD'" "$fsdb"
}

function _fsdb_clear_hashes {
  # First parameter is the path to fsdb
  local fsdb="$1" # required

  _gawk_inplace "'$AWK_FSDB_CLEAR_HASHES'" "$fsdb"
}


# Manuals:

function _show_manual_for {
  local function_name="$1" # required

  man "git-secret-${function_name}"
  exit 0
}


# Invalid options

function _invalid_option_for {
  local function_name="$1" # required

  man "git-secret-${function_name}"
  exit 1
}


# VCS:

function _check_ignore {
  local filename="$1" # required

  local result
  result="$(git add -n "$filename" > /dev/null 2>&1; echo $?)"
  # when ignored
  if [[ "$result" -ne 0 ]]; then
    result=0
  else
    result=1
  fi
  # returns 1 when not ignored, and 0 when ignored
  echo "$result"
}


function _git_normalize_filename {
  local filename="$1" # required

  local result
  result=$(git ls-files --full-name -o "$filename")
  echo "$result"
}


function _maybe_create_gitignore {
  # This function creates '.gitignore' if it was missing.

  local full_path
  full_path=$(_append_root_path '.gitignore')

  if [[ ! -f "$full_path" ]]; then
    touch "$full_path"
  fi
}


function _add_ignored_file {
  # This function adds a line with the filename into the '.gitgnore' file.
  # It also creates '.gitignore' if it's not there

  local filename="$1" # required

  _maybe_create_gitignore

  local full_path
  full_path=$(_append_root_path '.gitignore')

  echo "$filename" >> "$full_path"
}


function _is_inside_git_tree {
  # Checks if we are working inside the `git` tree.
  local result
  result=$(git rev-parse --is-inside-work-tree > /dev/null 2>&1; echo $?)

  echo "$result"
}


function _get_git_root_path {
  # We need this function to get the location of the `.git` folder,
  # since `.gitsecret` must be on the same level.

  local result
  result=$(git rev-parse --show-toplevel)
  echo "$result"
}


# Relative paths:

function _append_root_path {
  # This function adds root path to any other path.

  local path="$1" # required

  local root_path
  root_path=$(_get_git_root_path)

  echo "$root_path/$path"
}


function _get_secrets_dir {
  _append_root_path "${_SECRETS_DIR}"
}


function _get_secrets_dir_keys {
  _append_root_path "${_SECRETS_DIR_KEYS}"
}


function _get_secrets_dir_path {
  _append_root_path "${_SECRETS_DIR_PATHS}"
}


function _get_secrets_dir_keys_mapping {
  _append_root_path "${_SECRETS_DIR_KEYS_MAPPING}"
}


function _get_secrets_dir_keys_trustdb {
  _append_root_path "${_SECRETS_DIR_KEYS_TRUSTDB}"
}


function _get_secrets_dir_paths_mapping {
  _append_root_path "${_SECRETS_DIR_PATHS_MAPPING}"
}


# Logic:

function _get_gpg_local {
  # This function is required to return proper `gpg` command.
  # This function was created due to this bug:
  # https://github.com/sobolevn/git-secret/issues/85

  local homedir
  homedir=$(_get_secrets_dir_keys)

  local gpg_local="$SECRETS_GPG_COMMAND --homedir=$homedir --no-permission-warning"
  echo "$gpg_local"
}


function _abort {
  local message="$1" # required

  >&2 echo "$message abort."
  exit 1
}

function _find_and_clean {
  # required:
  local pattern="$1" # can be any string pattern

  # optional:
  local verbose=${2:-""} # can be empty or should be equal to "v"

  local root
  root=$(_get_git_root_path)

  # shellcheck disable=2086
  find "$root" -path "$pattern" -type f -print0 | xargs -0 rm -f$verbose
}


function _find_and_clean_formated {
  # required:
  local pattern="$1" # can be any string pattern

  # optional:
  local verbose=${2:-""} # can be empty or should be equal to "v"
  local message=${3:-"cleaning:"} # can be any string

  if [[ ! -z "$verbose" ]]; then
    echo && echo "$message"
  fi

  _find_and_clean "$pattern" "$verbose"

  if [[ ! -z "$verbose" ]]; then
    echo
  fi
}


function _list_all_added_files {
  local path_mappings
  path_mappings=$(_get_secrets_dir_paths_mapping)

  if [[ ! -s "$path_mappings" ]]; then
    _abort "$path_mappings is missing."
  fi

  while read -r line; do
    _get_record_filename "$line"
  done < "$path_mappings"
}


function _secrets_dir_exists {
  # This function checks if "$_SECRETS_DIR" exists and.

  local full_path
  full_path=$(_get_secrets_dir)

  if [[ ! -d "$full_path" ]]; then
    _abort "$full_path does not exist."
  fi
}


function _secrets_dir_is_not_ignored {
  # This function checks that "$_SECRETS_DIR" is not ignored.

  local git_secret_dir
  git_secret_dir=$(_get_secrets_dir)

  # Create git_secret_dir required for check
  local cleanup=0
  if [[ ! -d "$git_secret_dir" ]]; then
    mkdir "$git_secret_dir"
    cleanup=1
  fi
  local ignores
  ignores=$(_check_ignore "$git_secret_dir")
  if [[ "$cleanup" == 1 ]]; then
    rmdir "$git_secret_dir"
  fi

  if [[ ! $ignores -eq 1 ]]; then
    _abort "'$git_secret_dir' is ignored."
  fi
}


function _user_required {
  # This function does a bunch of validations:
  # 1. It calls `_secrets_dir_exists` to verify that "$_SECRETS_DIR" exists.
  # 2. It ensures that "$_SECRETS_DIR_KEYS_TRUSTDB" exists.
  # 3. It ensures that there are added public keys.

  _secrets_dir_exists

  local trustdb
  trustdb=$(_get_secrets_dir_keys_trustdb)

  local error_message="no users found. run 'git secret tell'."
  if [[ ! -f "$trustdb" ]]; then
    _abort "$error_message"
  fi

  local gpg_local
  gpg_local=$(_get_gpg_local)

  local keys_exist
  keys_exist=$($gpg_local -n --list-keys)
  if [[ -z "$keys_exist" ]]; then
    _abort "$error_message"
  fi
}


function _get_raw_filename {
  echo "$(dirname "$1")/$(basename "$1" "$SECRETS_EXTENSION")" | sed -e 's#^\./##'
}


function _get_encrypted_filename {
  local filename
  filename="$(dirname "$1")/$(basename "$1" "$SECRETS_EXTENSION")"
  echo "${filename}${SECRETS_EXTENSION}" | sed -e 's#^\./##'
}


function _parse_keyring_users {
  # First argument must be a `sed` pattern
  local sed_pattern="$1"

  local result

  local gpg_local
  gpg_local=$(_get_gpg_local)

  result=$($gpg_local --list-public-keys --with-colon | sed -n "$sed_pattern")
  echo "$result"
}


function _get_users_in_keyring {
  # This function is required to show the users in the keyring.
  # `whoknows` command uses it internally.
  # It basically just parses the `gpg` public keys

  _parse_keyring_users 's/.*<\(.*\)>.*/\1/p'
}


function _get_recepients {
  # This function is required to create an encrypted file for different users.
  # These users are called 'recepients' in the `gpg` terms.
  # It basically just parses the `gpg` public keys

  _parse_keyring_users 's/.*<\(.*\)>.*/-r\1/p'
}


function _decrypt {
  # required:
  local filename="$1"

  # optional:
  local write_to_file=${2:-1} # can be 0 or 1
  local force=${3:-0} # can be 0 or 1
  local homedir=${4:-""}
  local passphrase=${5:-""}

  local encrypted_filename
  encrypted_filename=$(_get_encrypted_filename "$filename")

  local base="$SECRETS_GPG_COMMAND --use-agent --decrypt --no-permission-warning"

  if [[ "$write_to_file" -eq 1 ]]; then
    base="$base -o $filename"
  fi

  if [[ "$force" -eq 1 ]]; then
    base="$base --yes"
  fi

  if [[ ! -z "$homedir" ]]; then
    base="$base --homedir=$homedir"
  fi

  if [[ "$GPG_VER_21" -eq 1 ]]; then
    base="$base --pinentry-mode loopback"
  fi

  if [[ ! -z "$passphrase" ]]; then
    echo "$passphrase" | $base --quiet --batch --yes --no-tty --passphrase-fd 0 \
      "$encrypted_filename"
  else
    $base --quiet "$encrypted_filename"
  fi
}
#!/usr/bin/env bash


function reveal {
  local homedir=''
  local passphrase=''
  local force=0

  OPTIND=1

  while getopts 'hfd:p:' opt; do
    case "$opt" in
      h) _show_manual_for 'reveal';;

      f) force=1;;

      p) passphrase=$OPTARG;;

      d) homedir=$OPTARG;;

      *) _invalid_option_for 'reveal';;
    esac
  done

  shift $((OPTIND-1))
  [ "$1" = '--' ] && shift

  _user_required

  # Command logic:

  local path_mappings
  path_mappings=$(_get_secrets_dir_paths_mapping)

  local counter=0
  while read -r line; do
    local filename
    local path
    filename=$(_get_record_filename "$line")
    path=$(_append_root_path "$filename")

    # The parameters are: filename, write-to-file, force, homedir, passphrase
    _decrypt "$path" "1" "$force" "$homedir" "$passphrase"

    counter=$((counter+1))
  done < "$path_mappings"

  echo "done. all $counter files are revealed."
}
#!/usr/bin/env bash


function add {
  local auto_ignore=0
  OPTIND=1

  while getopts "ih" opt; do
    case "$opt" in
      i) auto_ignore=1;;

      h) _show_manual_for "add";;

      *) _invalid_option_for "add";;
    esac
  done

  shift $((OPTIND-1))
  [ "$1" = "--" ] && shift

  _user_required

  # Checking if all files are correct (ignored and inside the repo):

  local not_ignored=()
  local items=( "$@" )

  # Checking if all files in options are ignored:
  for item in "${items[@]}"; do
    local path # absolute path
    local normalized_path # relative to the .git dir
    normalized_path=$(_git_normalize_filename "$item")
    path=$(_append_root_path "$normalized_path")

    # Checking that file is valid:
    if [[ ! -f "$path" ]]; then
      _abort "$item is not a file."
    fi

    # Checking that it is ignored:
    local ignored
    ignored=$(_check_ignore "$path")

    if [[ "$ignored" -ne 0 ]]; then
      # Collect unignored files:
      not_ignored+=("$normalized_path")
    fi
  done

  # Are there any uningnored files?

  if [[ ! "${#not_ignored[@]}" -eq 0 ]]; then
    # And show them all at once.
    local message
    message="these files are not ignored: $* ;"

    if [[ "$auto_ignore" -eq 0 ]]; then
      # This file is not ignored. user don't want it to be added automatically.
      # Raise the exception, since all files, which will be hidden, must be ignored.
      _abort "$message"
    else
      # In this case these files should be added to the `.gitignore` automatically:
      # see https://github.com/sobolevn/git-secret/issues/18 for more.
      echo "$message"
      echo "auto adding them to .gitignore"
      for item in "${not_ignored[@]}"; do
        _add_ignored_file "$item"
      done
    fi
  fi

  # Adding files to path mappings:

  local fsdb
  fsdb=$(_get_secrets_dir_paths_mapping)

  for item in "${items[@]}"; do
    local path
    local key
    path=$(_git_normalize_filename "$item")
    key="$path"

    # Adding files into system, skipping duplicates.
    local already_in
    already_in=$(_fsdb_has_record "$key" "$fsdb")
    if [[ "$already_in" -eq 1 ]]; then
      echo "$key" >> "$fsdb"
    fi
  done

  echo "${#@} item(s) added."
}
#!/usr/bin/env bash

# shellcheck disable=2016
AWK_FSDB_UPDATE_HASH='
BEGIN { FS=":"; OFS=":"; }
{
  if ( key == $1 )
  {
    print key,hash;
  }
  else
  {
    print $1,$2;
  }
}
'

function _optional_clean {
  local clean="$1"
  local verbose=${2:-""}

  if [[ $clean -eq 1 ]]; then
    _find_and_clean_formated "*$SECRETS_EXTENSION" "$verbose"
  fi
}


function _optional_delete {
  local delete="$1"
  local verbose=${2:-""}

  if [[ $delete -eq 1 ]]; then
    local path_mappings
    path_mappings=$(_get_secrets_dir_paths_mapping)

    # We use custom formating here:
    if [[ ! -z "$verbose" ]]; then
      echo && echo 'removing unencrypted files:'
    fi

    while read -r line; do
      # So the formating would not be repeated several times here:
      local filename
      filename=$(_get_record_filename "$line")
      _find_and_clean "*$filename" "$verbose"
    done < "$path_mappings"

    if [[ ! -z "$verbose" ]]; then
      echo
    fi
  fi
}

function _get_checksum_local {
  local checksum="$SECRETS_CHECKSUM_COMMAND"
  echo "$checksum"
}

function _get_file_hash {
  local input_path="$1" # Required
  local checksum_local
  local file_hash

  checksum_local="$(_get_checksum_local)"
  file_hash=$($checksum_local "$input_path" | gawk '{print $1}')

  echo "$file_hash"
}

function _optional_fsdb_update_hash {
  local key="$1"
  local hash="$2"
  local fsdb          # path_mappings

  fsdb=$(_get_secrets_dir_paths_mapping)

  _gawk_inplace -v key="$key" -v hash="$hash" "'$AWK_FSDB_UPDATE_HASH'" "$fsdb"
}


function hide {
  local clean=0
  local delete=0
  local fsdb_update_hash=0 # add checksum hashes to fsdb
  local verbose=''

  OPTIND=1

  while getopts 'cdmvh' opt; do
    case "$opt" in
      c) clean=1;;

      d) delete=1;;

      m) fsdb_update_hash=1;;

      v) verbose='v';;

      h) _show_manual_for 'hide';;

      *) _invalid_option_for 'hide';;
    esac
  done

  shift $((OPTIND-1))
  [ "$1" = '--' ] && shift

  # We need user to continue:
  _user_required

  # If -c option was provided, it would clean the hidden files
  # before creating new ones.
  _optional_clean "$clean" "$verbose"

  # Encrypting files:

  local path_mappings
  path_mappings=$(_get_secrets_dir_paths_mapping)

  local counter=0
  while read -r record; do
    local filename
    local fsdb_file_hash
    local encrypted_filename
    filename=$(_get_record_filename "$record")
    fsdb_file_hash=$(_get_record_hash "$record")
    encrypted_filename=$(_get_encrypted_filename "$filename")

    local recipients
    recipients=$(_get_recepients)

    local gpg_local
    gpg_local=$(_get_gpg_local)

    local input_path
    local output_path
    input_path=$(_append_root_path "$filename")
    output_path=$(_append_root_path "$encrypted_filename")

    file_hash=$(_get_file_hash "$input_path")

    # encrypt file only if required
    if [[ "$fsdb_file_hash" != "$file_hash" ]]; then
      # shellcheck disable=2086
      $gpg_local --use-agent --yes --trust-model=always --encrypt \
        $recipients -o "$output_path" "$input_path" > /dev/null 2>&1
      # If -m option was provided, it will update unencrypted file hash
      local key="$filename"
      local hash="$file_hash"
      # Update file hash if required in fsdb
      [[ "$fsdb_update_hash" -gt 0 ]] && \
        _optional_fsdb_update_hash "$key" "$hash"
    fi
    counter=$((counter+1))
  done < "$path_mappings"

  # If -d option was provided, it would delete the source files
  # after we have already hidden them.
  _optional_delete "$delete" "$verbose"

  echo "done. all $counter files are hidden."
}
#!/usr/bin/env bash


function whoknows {
  OPTIND=1

  while getopts "h?" opt; do
    case "$opt" in
      h) _show_manual_for "whoknows";;

      *) _invalid_option_for "whoknows";;
    esac
  done

  shift $((OPTIND-1))
  [ "$1" = "--" ] && shift

  # Validating, that we have a user:
  _user_required

  local keys

  # Getting the users from gpg:
  keys=$(_get_users_in_keyring)
  echo "$keys"
}
#!/usr/bin/env bash


function remove {
  local clean=0

  OPTIND=1

  while getopts 'ch' opt; do
    case "$opt" in
      c) clean=1;;

      h) _show_manual_for 'remove';;

      *) _invalid_option_for 'remove';;
    esac
  done

  shift $((OPTIND-1))
  [ "$1" = '--' ] && shift

  # Validate if user exists:
  _user_required

  # Command logic:

  local path_mappings
  path_mappings=$(_get_secrets_dir_paths_mapping)

  for item in "$@"; do
    local path # absolute path
    local normalized_path # relative to .git folder
    normalized_path=$(_git_normalize_filename "$item")
    path=$(_append_root_path "$normalized_path")

    echo "$item -> $normalized_path -> $path"

    # Checking if file exists:
    if [[ ! -f "$path" ]]; then
      _abort "$item is not a file."
    fi

    # Deleting it from path mappings:
    # Remove record from fsdb with matching key
    local key
    key="$normalized_path"
    fsdb="$path_mappings"
    _fsdb_rm_record "$key" "$fsdb"

    rm -f "${path_mappings}.bak"  # not all systems create '.bak'

    # Optional clean:
    if [[ "$clean" -eq 1 ]]; then
      local encrypted_filename
      encrypted_filename=$(_get_encrypted_filename "$path")

      rm "$encrypted_filename" # fail on error
    fi
  done

  echo 'removed from index.'
  echo "ensure that files: [$*] are now not ignored."
}
#!/usr/bin/env bash

# shellcheck disable=2016
AWK_ADD_TO_GITIGNORE='
BEGIN {
  cnt=0
}

function check_print_line(line){
  if (line == pattern) {
    cnt++
  }
  print line
}

# main function
{
  check_print_line($0)      # check and print first line
  while (getline == 1) {    # check and print all other
    check_print_line($0)
  }
}

END {
  if ( cnt == 0) {         # if file did not contain pattern add
    print pattern
  }
}
'

function gitignore_add_pattern {
  local pattern
  local gitignore_file_path

  pattern="$1"
  gitignore_file_path=$(_append_root_path '.gitignore')

  _maybe_create_gitignore
  _gawk_inplace -v pattern="$pattern" "'$AWK_ADD_TO_GITIGNORE'" "$gitignore_file_path"
}

function init {
  OPTIND=1

  while getopts 'h' opt; do
    case "$opt" in
      h) _show_manual_for 'init';;

      *) _invalid_option_for 'init';;
    esac
  done

  shift $((OPTIND-1))
  [ "$1" = '--' ] && shift

  # Check if '.gitsecret/' already exists:
  local git_secret_dir
  git_secret_dir=$(_get_secrets_dir)

  if [[ -d "$git_secret_dir" ]]; then
    _abort 'already inited.'
  fi

  # Check if it is ignored:
  _secrets_dir_is_not_ignored

  # Create internal files:

  mkdir "$git_secret_dir" "$(_get_secrets_dir_keys)" "$(_get_secrets_dir_path)"
  touch "$(_get_secrets_dir_keys_mapping)" "$(_get_secrets_dir_paths_mapping)"

  echo "'$git_secret_dir/' created."

  local random_seed_file
  random_seed_file=".gitsecret/keys/random_seed"
  gitignore_add_pattern "$random_seed_file"

  # TODO: git attributes to view diffs
}
#!/usr/bin/env bash


function clean {
  local verbose=''

  OPTIND=1

  while getopts 'vh' opt; do
    case "$opt" in
      v) verbose="v";;

      h) _show_manual_for 'clean';;

      *) _invalid_option_for 'clean';;
    esac
  done

  shift $((OPTIND-1))
  [ "$1" = '--' ] && shift

  _user_required

  # User should see properly formated output:
  _find_and_clean_formated "*$SECRETS_EXTENSION" "$verbose"
}
#!/usr/bin/env bash


function killperson {
  OPTIND=1

  while getopts 'h' opt; do
    case "$opt" in
      h) _show_manual_for 'killperson';;

      *) _invalid_option_for 'killperson';;
    esac
  done

  shift $((OPTIND-1))
  [ "$1" = "--" ] && shift

  _user_required

  # Command logic:

  local emails=( "$@" )

  if [[ ${#emails[@]} -eq 0 ]]; then
    _abort "at least one email is required."
  fi

  # Getting the local `gpg` command:
  local gpg_local
  gpg_local=$(_get_gpg_local)

  for email in "${emails[@]}"; do
    $gpg_local --batch --yes --delete-key "$email"
  done

  echo 'removed keys.'
  echo "now [$*] do not have an access to the repository."
  echo 'make sure to hide the existing secrets again.'
}
#!/usr/bin/env bash


function usage {
  OPTIND=1

  while getopts "h?" opt; do
    case "$opt" in
      h) _show_manual_for "usage";;

      *) _invalid_option_for "usage";;
    esac
  done

  shift $((OPTIND-1))
  [ "$1" = "--" ] && shift

  # There was a bug with some shells, which were adding extra commands
  # to the old dynamic-loading version of this code.
  # thanks to @antmak it is now fixed, see:
  # https://github.com/sobolevn/git-secret/issues/47
  local commands="add|changes|clean|hide|init|killperson|list|remove|reveal|tell|usage|whoknows"

  echo "usage: git secret [$commands]"
}
#!/usr/bin/env bash

function changes {
  local passphrase=""

  OPTIND=1

  while getopts 'hd:p:' opt; do
    case "$opt" in
      h) _show_manual_for 'changes';;

      p) passphrase=$OPTARG;;

      d) homedir=$OPTARG;;

      *) _invalid_option_for 'changes';;
    esac
  done

  shift $((OPTIND-1))
  [ "$1" = '--' ] && shift

  _user_required

  local filenames="$*"
  if [[ -z "$filenames" ]]; then
    # Checking if no filenames are passed, show diff for all files.
    filenames=$(_list_all_added_files)
  fi

  IFS='
  '

  for filename in $filenames; do
    local decrypted
    local diff_result

    local path # absolute path
    local normalized_path # relative to the .git dir
    normalized_path=$(_git_normalize_filename "$filename")

    if [[ ! -z "$normalized_path" ]]; then
      path=$(_append_root_path "$normalized_path")
    else
      # Path was already normalized
      path=$(_append_root_path "$filename")
    fi

    # Now we have all the data required:
    decrypted=$(_decrypt "$path" "0" "0" "$homedir" "$passphrase")

    # Let's diff the result:
    diff_result=$(diff -u <(echo "$decrypted") "$path") || true
    # There was a bug in the previous version, since `diff` returns
    # exit code `1` when the files are different.
    echo "changes in ${path}:"
    echo "${diff_result}"
  done
}
#!/usr/bin/env bash

# shellcheck disable=2016
AWK_GPG_KEY_CNT='
BEGIN { cnt=0; OFS=":"; FS=":"; }
flag=0; $1 == "pub" { cnt++ }
END { print cnt }
'

function get_gpg_key_count {
  local gpg_local
  gpg_local=$(_get_gpg_local)
  $gpg_local --list-public-keys --with-colon | gawk "$AWK_GPG_KEY_CNT"
}

function tell {
  local emails
  local self_email=0
  local homedir

  # A POSIX variable
  # Reset in case getopts has been used previously in the shell.
  OPTIND=1

  while getopts "hmd:" opt; do
    case "$opt" in
      h) _show_manual_for "tell";;

      m) self_email=1;;

      d) homedir=$OPTARG;;

      *) _invalid_option_for 'tell';;
    esac
  done

  shift $((OPTIND-1))
  [ "$1" = "--" ] && shift

  # Validates that application is inited:
  _secrets_dir_exists

  # Command logic:
  emails=( "$@" )
  local git_email

  if [[ "$self_email" -eq 1 ]]; then
    git_email=$(git config user.email)

    if [[ -z "$git_email" ]]; then
      _abort "'git config user.email' is not set."
    fi

    emails+=("$git_email")
  fi

  if [[ "${#emails[@]}" -eq 0 ]]; then
    # If after possible addition of git_email, emails are still empty,
    # we should raise an exception.
    _abort "you must provide at least one email address."
  fi

  local start_key_cnt
  start_key_cnt=$(get_gpg_key_count)
  for email in "${emails[@]}"; do
    # This file will be removed automatically:
    _temporary_file  # note, that `_temporary_file` will export `filename` var.
    # shellcheck disable=2154
    local keyfile="$filename"

    if [[ -z "$homedir" ]]; then
      $SECRETS_GPG_COMMAND --export -a "$email" > "$keyfile"
    else
      # It means that homedir is set as an extra argument via `-d`:
      $SECRETS_GPG_COMMAND --no-permission-warning --homedir="$homedir" \
        --export -a "$email" > "$keyfile"
    fi

    if [[ ! -s "$keyfile" ]]; then
      _abort 'gpg key is empty. check your key name: "gpg --list-keys".'
    fi

    # Importing public key to the local keychain:
    local gpg_local
    gpg_local=$(_get_gpg_local)
    $gpg_local --import "$keyfile" > /dev/null 2>&1
  done

  echo "done. ${emails[*]} added as someone who know(s) the secret."

  # force re-encrypting of files if required
  local fsdb
  local end_key_cnt
  fsdb=$(_get_secrets_dir_paths_mapping)
  end_key_cnt=$(get_gpg_key_count)
  [[ $start_key_cnt -ne $end_key_cnt ]] && _fsdb_clear_hashes "$fsdb"
}
#!/usr/bin/env bash


function list {
  OPTIND=1

  while getopts 'h' opt; do
    case "$opt" in
      h) _show_manual_for 'list';;

      *) _invalid_option_for 'list';;
    esac
  done

  shift $((OPTIND-1))
  [ "$1" = '--' ] && shift

  _user_required

  # Command logic:
  _list_all_added_files
}
#!/usr/bin/env bash

set -e

function _check_setup {
  # Checking git and secret-plugin setup:
  local is_tree
  is_tree=$(_is_inside_git_tree)
  if [[ "$is_tree" -ne 0 ]]; then
    _abort "repository is broken. try running 'git init' or 'git clone'."
  fi

  # Checking if the '.gitsecret' is not ignored:
  _secrets_dir_is_not_ignored

  # Checking gpg setup:
  local keys_dir
  keys_dir=$(_get_secrets_dir_keys)

  local secring="$keys_dir/secring.gpg"
  if [[ -f $secring ]] && [[ -s $secring ]]; then
    # secring.gpg exists and is not empty,
    # someone has imported a private key.
    _abort 'it seems that someone has imported a secret key.'
  fi
}


function _incorrect_usage {
  echo "$1"
  usage
  exit "$2"
}


function _show_version {
  echo "$GITSECRET_VERSION"
  exit 0
}


function _init_script {
  if [[ $# == 0 ]]; then
    _incorrect_usage 'no input parameters provided.' 126
  fi

  # Parse plugin-level options:
  local dry_run=0

  while [[ $# -gt 0 ]]; do
    local opt="$1"

    case "$opt" in
      # Options for quick-exit strategy:
      --dry-run)
        dry_run=1
        shift;;

      --version) _show_version;;

      *) break;;  # do nothing
    esac
  done

  if [[ "$dry_run" == 0 ]]; then
    # Checking for proper set-up:
    _check_setup

    # Routing the input command:
    local function_exists
    function_exists=$(_function_exists "$1")

    if [[ "$function_exists" == 0 ]] && [[ ! $1 == _* ]]; then
      $1 "${@:2}"
    else  # TODO: elif [[ $(_plugin_exists $1) == 0 ]]; then
      _incorrect_usage "command $1 not found." 126
    fi
  fi
}


_init_script "$@"
