diff --git a/Taskfile.yaml b/Taskfile.yaml new file mode 120000 index 0000000000000000000000000000000000000000..be388a9b6f3cb9f81816a8a556c5329e9b472e3c --- /dev/null +++ b/Taskfile.yaml @@ -0,0 +1 @@ +/nix/store/iyrsyrz5b4g0sqcinrck6sk1diy868x0-Taskfile.yaml \ No newline at end of file diff --git a/Taskfile.yml b/Taskfile.yml deleted file mode 100644 index adca507c01815ac863d31cb599de1917770a0498..0000000000000000000000000000000000000000 --- a/Taskfile.yml +++ /dev/null @@ -1,24 +0,0 @@ -version: '3' - -tasks: - - default: - aliases: - - help - cmds: - - task --list - - build: - desc: Build the app - aliases: - - b - vars: - DEVENV_ROOT: - sh: echo "$DEVENV_ROOT" - - cmds: - - devenv shell build-app - sources: - - source/**/*.go - - source/**/*.mod - - dist/** diff --git a/devenv.nix b/devenv.nix index 41fa742225642b676a641894c5cfd8ab6500e17b..b919e059e404255fbcd751b729dac7c120d2470c 100644 --- a/devenv.nix +++ b/devenv.nix @@ -1,40 +1,149 @@ # See full reference at https://devenv.sh/reference/options/ { pkgs, inputs, phps, lib, config, modulesPath,... }: +let + + taskfileYaml = pkgs.writeTextFile { + name = "Taskfile.yaml"; + text = '' +## THIS FILE IS AUTOGENERATED. DO NOT EDIT THIS FILE DIRECTLY. +version: '3' + +tasks: + build: + desc: Build the app + aliases: + - b + vars: + DEVENV_ROOT: + sh: echo "$DEVENV_ROOT" + + cmds: + - devenv shell build-app + sources: + - source/**/*.go + - source/**/*.mod + - dist/** + + commit: + desc: Commit a feature + cmds: + - do-git-commit + silent: true + + default: + desc: Print this help message + aliases: [d, help] + cmds: + - task --list + silent: true + + ''; + }; + + + in + + { env.APP_NAME = "version"; # https://devenv.sh/packages/ - packages = [ + packages = with pkgs; [ inputs.version.defaultPackage."${builtins.currentSystem}" - pkgs.git - pkgs.gcc12 - pkgs.go-task - pkgs.blackbox - pkgs.blackbox-terminal - pkgs.jq - pkgs.delve - pkgs.gdlv - pkgs.wget - pkgs.glab - pkgs.unixtools.xxd - pkgs.libffi - pkgs.zlib - pkgs.procps - pkgs.php81Extensions.xdebug - pkgs.ranger - pkgs.meld - pkgs.gnused - pkgs.coreutils-full - pkgs.gnugrep - pkgs.gnumake - pkgs.util-linux - pkgs.httpie - pkgs.netcat - pkgs.memcached - pkgs.fd + appimage-run + awscli2 + blackbox + blackbox-terminal + coreutils-full + d2 + dbeaver + delve + dialog + dive + docker-client + docker-compose + drill + exa + fd + gawk + gcc12 + gdlv + git + glab + gnugrep + gnum4 + gnumake + gnupg + gnused + go-task + graphviz + gum + httpie + hurl + jq + libffi + libffi + logrotate + mdbook + mdbook-admonish + mdbook-cmdrun + mdbook-d2 + mdbook-emojicodes + mdbook-graphviz + mdbook-linkcheck + mdbook-mermaid + mdbook-pdf + meld + memcached + netcat + netcat + oha + openai + oxker + pandoc-katex + plantuml + pprof + procps + ranger + termbook + unixtools.arp + unixtools.xxd + unzip + util-linux + wget + zlib + ]; + + scripts.update-files.exec = '' + update_symlink() { + local source_path="$1" + local target_path="$2" + local file_name="$(basename "$target_path")" + + if [[ -L "$source_path" ]]; then + if [[ "$(readlink "$source_path")" != "$target_path" ]]; then + # Link exists but is not up to date + rm "$source_path" + ln -s "$target_path" "$source_path" + fi + elif [[ -e "$source_path" ]]; then + echo "$file_name already exists. Please rename or delete it." + exit 1 + else + ln -s "$target_path" "$source_path" + fi + } + + # Usage for Taskfile.yaml + update_symlink "${config.devenv.root}/Taskfile.yaml" "${taskfileYaml}" + + + + + ''; # https://devenv.sh/languages/ @@ -182,5 +291,341 @@ echo "done" ''; + scripts.do-git-commit.exec = '' + #!${pkgs.bash}/bin/bash + + # Define colors if the terminal supports it + if [ -t 1 ]; then + RED='\033[0;31m' + GREEN='\033[0;32m' + RESET='\033[0m' + BOLD='\033[1m' + else + RED="" + GREEN="" + RESET="" + fi + + step=1 + + reset + clear + + # create random log file + LOGFILE="$(mktemp)" + if [ $? -ne 0 ]; then + echo -e "''${RED}✖ Could not create temporary log file. Exiting.''${RESET}" + exit 1 + fi + + log_and_display() { + echo -e "''${GREEN}==> $step. $1''${RESET}" | tee -a $LOGFILE + step=$((step + 1)) + } + + log_error_and_display() { + echo -e "''${RED}==> $step. $1''${RESET}" | tee -a $LOGFILE + } + + printLogfileAndExit() { + exit_code=$1 + echo -e "\n\n========================================\n\n\n" + + echo -e "\n\n''${BOLD}Git and GitLab Automation Script''${RESET}\n\nI have performed the following actions:\n\n" + cat "$LOGFILE" + + # Optional: Remove log file + ${pkgs.coreutils}/bin/rm -f "$LOGFILE" + + if [ $exit_code -eq 0 ]; then + echo -e "\n''${GREEN}✔''${RESET} All actions were successful" | tee -a $LOGFILE + elif [ $exit_code -eq -1 ]; then + echo -e "\n''${RED}✖''${RESET} The script was manually cancelled" | tee -a $LOGFILE + exit_code=0 + else + echo -e "\n''${RED}✖''${RESET} Some actions failed" | tee -a $LOGFILE + fi + + exit $exit_code + } + + print_headline() { + local title=$1 + local underline=$(printf '─%.0s' $(seq 1 ''${#title})) + echo -e "\n\n''${BOLD}''${title}\n''${underline}''${RESET}\n" + } + + do_cancel() { + echo -e "''${RED}==> ✖ Cancelled.''${RESET}" | tee -a $LOGFILE + printLogfileAndExit -1 + } + + # Function for unified logging and display + log_action() { + if [ $? -eq 0 ]; then + echo -e " ''${GREEN}✔''${RESET} $1: Successful" | tee -a $LOGFILE + else + echo -e " ''${RED}✖''${RESET} $1: Failed" | tee -a $LOGFILE + printLogfileAndExit 1 + fi + } + + print_what_to_do() { + echo -e "\n\nWhat do you want to do?\n" + } + + git_status=$(git status --porcelain) + if [[ -z "$git_status" ]]; then + log_error_and_display "No changes to commit. Exiting." + printLogfileAndExit 0 + fi + + print_headline "Choose commit type" + selection=$(gum choose "feat: (new feature for the user, not a new feature for build script)" "fix: (bug fix for the user, not a fix to a build script)" "chore: (updating grunt tasks etc.; no production code change)" "docs: (changes to the documentation)" "style: (formatting, missing semi colons, etc; no production code change)" "refactor: (refactoring production code, eg. renaming a variable)" "test: (adding missing tests, refactoring tests; no production code change)" "Cancel") + + commit_type=$(echo "$selection" | awk -F':' '{print $1}') + + if [[ "$commit_type" == "Cancel" ]]; then + do_cancel + fi + + log_and_display "You chose the commit type: $commit_type" + + # NEXT STEP ISSUE HANDLING ############################################################################################################ + #log_and_display "Issue handling" + + gitlabIssues=() + while IFS= read -r line; do + if [[ $line =~ ^# ]]; then + id=$(echo "$line" | awk '{print substr($1, 2)}') + title=$(echo "$line" | awk -F'about' '{print $1}' | awk '{$1=$2=""; print substr($0, 3)}') + gitlabIssues+=("$id > $title") + fi + done < <(gum spin --spinner dot --show-output --title "Ask gitlab ..." -- glab issue list --output-format=details) + + ## if issues are available, ask if user wants to use an existing issue or create a new one + createOption="Create new issue" + existingOption="Use existing issue" + cancelOption="Cancel" + + print_headline "Choose issue handling" + if [ ''${#gitlabIssues[@]} -eq 0 ]; then + log_and_display "There are no issues available." + + print_what_to_do + choice=$(gum choose "$createOption" "$cancelOption") + + else + log_and_display "There are ''${#gitlabIssues[@]} issues available." + print_what_to_do + choice=$(gum choose "$createOption" "$existingOption" "$cancelOption") + fi + + if [[ "$choice" == "$cancelOption" ]]; then + do_cancel + fi + + ## array of issue ids + work_on_issue_ids=() + + issue_text="" + + if [[ "$choice" == "$createOption" ]]; then + print_headline "Create new issue" + issue_text=$(gum input --placeholder "Enter issue title") + echo -e "Enter issue description. ''${RED}End with Ctrl+D''${RESET}\n" + issue_description=$(gum write --placeholder "Enter issue description. End with Ctrl+D") + + if [[ -z "$issue_text" ]]; then + log_error_and_display "Issue title is empty. Exiting." + printLogfileAndExit 1 + fi + + log_and_display "You entered the issue title: $issue_text" + log_and_display "You entered the issue description: $issue_description" + echo -e "\n" + + gum confirm "Do you want to create this issue?" + # gum confirm exits with status 0 if confirmed and status 1 if cancelled. + if [ $? -eq 1 ]; then + do_cancel + fi + + issue_output=$(glab issue create -t"$issue_text" --no-editor --description "$issue_description") + issue_id=$(echo "$issue_output" | grep -oP '(?<=/issues/)\d+') + + work_on_issue_ids+=("$issue_id") + + log_action "glab issue with id $issue_id created" + + else + + print_headline "Use existing issue" + echo -e "Select issue with arrow keys and press tab or space to select. Press enter to confirm.\n" + issue_ids=$(gum choose --no-limit "''${gitlabIssues[@]}") + + # assign issue_ids to work_on_issue_ids. iterate over lines and take integer from beginning of line + while IFS= read -r line; do + work_on_issue_ids+=($(echo "$line" | grep -oP '^\d+')) + done <<<"$issue_ids" + + fi + + if [ ''${#work_on_issue_ids[@]} -eq 0 ]; then + log_and_display "No issue selected. Exiting." + printLogfileAndExit 0 + fi + + # NEXT STEP COMMIT MESSAGE ############################################################################################################ + # print work_on_issue_ids + work_on_issue_ids_string="" + for i in "''${work_on_issue_ids[@]}"; do + work_on_issue_ids_string+="#$i " + done + + log_and_display "You chose to work on the following issues: ''${work_on_issue_ids_string}" + + + print_headline "Check for changes to commit" + + # ' ' = unmodified + # M = modified + # T = file type changed (regular file, symbolic link or submodule) + # A = added + # D = deleted + # R = renamed + # C = copied (if config option status.renames is set to "copies") + # U = updated but unmerged + # https://man.freebsd.org/cgi/man.cgi?query=git-status&sektion=1&manpath=freebsd-release-ports + + count_all_changes=$(echo "$git_status" | wc -l) + count_staged_changes=$(echo "$git_status" | grep -c '^M') + count_new_staged_files=$(echo "$git_status" | grep -c '^A') + count_staged_changes=$((count_staged_changes + count_new_staged_files)) + + git_options_all="All $count_all_changes changes" + git_options_staged="Only the $count_staged_changes staged changes" + git_options_select_files="Select files" + git_options_cancel="Cancel" + + git_options_array=() + if [[ $count_all_changes -gt 0 ]]; then + git_options_array+=("$git_options_all") + fi + + if [[ $count_staged_changes -gt 0 ]]; then + git_options_array+=("$git_options_staged") + fi + + git_options_array+=( "$git_options_select_files" ) + git_options_array+=( "$git_options_cancel" ) + + + selection=$(gum choose "''${git_options_array[@]}") + if [[ "$selection" == "$git_options_cancel" ]]; then + do_cancel + fi + + if [[ "$selection" == "$git_options_all" ]]; then + git add -A + echo "1" + elif [[ "$selection" == "$git_options_select_files" ]]; then + + files=() + while IFS= read -r line; do + files+=("$line") + done <<<"$git_status" + + selected_files=$(gum choose --no-limit "''${files[@]}") + + # no files selected + if [[ -z "$selected_files" ]]; then + log_and_display "No files selected. Exiting." + printLogfileAndExit 0 + fi + + # add selected files + while IFS= read -r line; do + ## git proclimne could have letter, ? or ! at the beginning of the line + file=$(echo "$line" | awk '{print $2}') + if [[ -z "$file" || ! -f "$file" ]]; then + log_and_display "No file found in line: $line" + continue + fi + + git add "$file" + done <<<"$selected_files" + + fi + + ## count staged changes again and print + count_staged_changes=$(echo "$git_status" | grep -c '^M') + count_new_staged_files=$(echo "$git_status" | grep -c '^A') + count_staged_changes=$((count_staged_changes + count_new_staged_files)) + + log_and_display "You have $count_staged_changes staged changes to commit." + + # NEXT STEP COMMIT MESSAGE ############################################################################################################ + + print_headline "Enter commit message" + commit_message=$(gum input --placeholder "Enter commit message" --value "$commit_type: $issue_text $work_on_issue_ids_string") + + if [[ -z "$commit_message" ]]; then + log_error_and_display "Commit message is empty. Exiting." + printLogfileAndExit 1 + fi + + log_and_display "You entered the commit message: $commit_message" + + gum confirm "Do you want to commit with this message?" + if [ $? -eq 1 ]; then + do_cancel + fi + + # NEXT STEP COMMIT #################################################################################################################### + print_headline "Committing changes" + + if ! git commit -m "$commit_message" ; then + log_error_and_display "Commit failed. Exiting." + printLogfileAndExit 1 + fi + + log_and_display "Commit successful." + + # NEXT STEP PUSH ###################################################################################################################### + + print_headline "Pushing changes" + + if ! git push ; then + log_error_and_display "Push failed. Exiting." + printLogfileAndExit 1 + fi + + log_and_display "Push successful." + + # Close issue ###################################################################################################################### + + print_headline "Closing issues" + + for issue_id in "''${work_on_issue_ids[@]}"; do + + gum confirm "Do you want to close issue #$issue_id?" + if [ $? -eq 1 ]; then + continue + fi + + if ! glab issue close "$issue_id" ; then + log_error_and_display "Closing issue $issue_id failed. Exiting." + else + log_and_display "Closing issue $issue_id successful." + fi + done + + printLogfileAndExit 0 + ''; + + enterShell = '' + update-files + ''; } diff --git a/source/errors.go b/source/errors.go index 9a9b0640ccdef7352fb724860c5af53001a980a2..73afad75ac6313103e222dae1b09990e41a98007 100644 --- a/source/errors.go +++ b/source/errors.go @@ -9,4 +9,5 @@ var ( noSemverError = fmt.Errorf("no semver") notFoundError = fmt.Errorf("not found") multipleFoundError = fmt.Errorf("multiple found") + ErrStopIteration = fmt.Errorf("stop iteration") ) diff --git a/source/git.go b/source/git.go index e7eb2a8d9cf5a860e2646df42841bd86f0d3e8bc..d4c8404092c41f36faa8cb63d23b59d641ddb357 100644 --- a/source/git.go +++ b/source/git.go @@ -127,23 +127,34 @@ func getTagCommit(tag string) (*object.Commit, error) { var tagCommit *object.Commit err = tags.ForEach(func(t *plumbing.Reference) error { if t.Name().Short() == tag { + tagObj, err := r.TagObject(t.Hash()) - if err != nil { - return err - } - tagCommit, err = tagObj.Commit() - if err != nil { + if err == plumbing.ErrObjectNotFound { + commit, err := r.CommitObject(t.Hash()) + if err != nil { + return err + } + tagCommit = commit + } else if err != nil { return err + } else { + + tagCommit, err = tagObj.Commit() + if err != nil { + return err + } } - return storer.ErrStop // stop iteration + return ErrStopIteration } return nil }) - if err != nil { + + if err != nil && err != ErrStopIteration { return nil, fmt.Errorf("failed to iterate over tags: %v", err) } + if tagCommit == nil { - return nil, fmt.Errorf("tag commit not found in commit log") + return nil, fmt.Errorf("tag '%s' not found in commit log", tag) } return tagCommit, nil