set -euo pipefail trap 'echo "Interrupted or failed. Repo may be mid-rebase/cherry-pick." ; exit 1' INT ERR if [[ $# -eq 0 ]]; then echo "Usage: ngcp [mode] [options]" echo "Run 'ngcp --help' for more information." exit 1 fi # waits for an internet connection. It pings both Google DNS and Cloudfare dns in case one of them is down pings=0 until ping -c1 -W1 1.1.1.1 >/dev/null 2>&1 || \ ping -c1 -W1 8.8.8.8 >/dev/null 2>&1 do sleep 1 ((pings++)) if (( pings > 2 )); then echo "No internet connection. Retrying in 1 second..." fi done CONFIG="$HOME/.config/nix-git-cherry-picker/config.json" if [[ ! -f "$CONFIG" ]]; then echo "Error: Config file not found at $CONFIG" echo "Please create it with the necessary settings." exit 1 fi localBranch=$(jq -r '.localBranch' "$CONFIG") remoteBranch=$(jq -r '.remoteBranch' "$CONFIG") nixConfigPath=$(jq -r '.nixConfigPath' "$CONFIG") automatic=0 # if there is the flag --automatic it will fail when merge conflict and tell you to do it manually. # if there is the flag --no-rebuild it won't rebuild your system to test the new commits # if there is the flag --continue-with-rebuild it will rebuild if --no-rebuild was set for flag in "$@"; do if [[ "$flag" == "--automatic" ]]; then automatic=1 elif [[ "$flag" == "--help" ]]; then echo "Usage: ngcp [mode] [options]" echo "Mode:" echo " pick <...> Cherry-pick commits for the remote branch." echo " pick -i [n] Interactively select from the last 10 (or n) commits." echo " pick --interactive [n] Interactively select from the last 10 (or n) commits." echo " last Cherry-pick the last n commits." echo " pull Pulls the changes to the local branch." echo "Options:" echo " --automatic Exit with no changes if merge conflict and instructs the user to pull manually. Use this option for automation." exit fi done # checks if connection if [[ "$1" == "pick" || "$1" == "last" ]]; then interactive=0 interactive_count=10 if [[ "$1" == "pick" && "${2:-}" == "-i" || "$1" == "pick" && "${2:-}" == "--interactive" ]]; then interactive=1 if [[ -n "${3:-}" ]]; then if ! [[ "$3" =~ ^[1-9][0-9]*$ ]]; then echo "Invalid interactive commit count: $3" exit 1 fi interactive_count="$3" fi fi if (( interactive )); then if ! command -v fzf >/dev/null 2>&1; then echo "Error: fzf is required for interactive mode." echo "Please install fzf and try again." exit 1 fi mapfile -t commits < <( git -C "$nixConfigPath" log \ --format='%H%x09%h%x09%s' \ -n "$interactive_count" \ HEAD | fzf \ --multi \ --reverse \ --header="TAB select/deselect • ENTER confirm" \ --preview="git -C '$nixConfigPath' show --stat --oneline {1}" | cut -f1 ) if [[ "${#commits[@]}" -eq 0 ]]; then echo "No commits selected." exit 0 fi # fzf displays newest -> oldest, but cherry-picking should happen # oldest -> newest. mapfile -t commits < <(printf '%s\n' "${commits[@]}" | tac) elif [[ "$1" == "last" ]]; then if [[ "$#" -lt 2 ]] || ! [[ "$2" =~ ^[1-9][0-9]*$ ]]; then echo "Invalid arguments. Usage: ngcp last " exit 1 fi mapfile -t commits < <( git -C "$nixConfigPath" log --format='%H' --reverse -n "$2" HEAD ) echo "Cherry-picking the last ${#commits[@]} commits:" printf ' %s\n' "${commits[@]}" else commits=("${@:2}") fi #if doesnt exist, just get the branch from the remote if ! git -C "$nixConfigPath" checkout "$remoteBranch"; then git -C "$nixConfigPath" checkout -b "$remoteBranch" "origin/$remoteBranch" fi echo "Pulling latest commits before cherry-picking..." if git -C "$nixConfigPath" pull --rebase ; then echo "Pull successful." elif (( automatic )); then echo "Merge conflict detected during pull. Please run ngcp pull manually" notify-send "Merge conflict detected during ngcp pull." "Please run ngcp pull manually" git -C "$nixConfigPath" rebase --abort exit 1 else echo "Merge conflict detected during pull." echo "Resolve conflicts and run:" echo " git rebase --continue" echo "Type 'y' when done." while git -C "$nixConfigPath" rev-parse --verify REBASE_HEAD >/dev/null 2>&1; do read -r answer [[ "$answer" == "y" ]] || continue done echo "Pull successful" fi for commit in "${commits[@]}"; do if git -C "$nixConfigPath" cherry-pick "$commit"; then echo "Applied $commit successfully" else echo "Cherry-pick of $commit failed." echo "Resolve conflicts and run:" echo " git cherry-pick --continue" echo echo "Type 'y' when done." while git -C "$nixConfigPath" rev-parse --verify CHERRY_PICK_HEAD >/dev/null 2>&1; do read -r answer [[ "$answer" == "y" ]] || continue done echo "Cherry-pick for $commit resolved." fi done git -C "$nixConfigPath" push --force origin "$remoteBranch" git -C "$nixConfigPath" checkout "$localBranch" elif [[ "$1" == "pull" ]]; then gitStatus=$(git -C "$nixConfigPath" status --porcelain) if [[ -n "$gitStatus" ]]; then echo "Git directory is dirty. Please clean it up before running ngcp pull" echo "$gitStatus" notify-send "Git directory is dirty." "Please clean it up before running ngcp pull\n $gitStatus" exit 1 fi if git -C "$nixConfigPath" pull --rebase ; then echo "Pull successful" else if (( automatic )); then git -C "$nixConfigPath" rebase --abort || true echo "git pull failed. Please run ngcp pull manually" notify-send "Git pull failed." "Please run ngcp pull manually" exit 1 else echo "Merge conflict detected during pull." echo "Resolve conflicts and run:" echo " git rebase --continue" echo "Type 'y' when done." while git -C "$nixConfigPath" rev-parse --verify REBASE_HEAD >/dev/null 2>&1; do read -r answer [[ "$answer" == "y" ]] || continue done echo "Pull successful" fi fi git -C "$nixConfigPath" push --force origin "$localBranch" else echo "Invalid argument. Please run ngcp --help" exit fi