From 2cc947e3d356554f307e69e37ec271a8e7b404e3 Mon Sep 17 00:00:00 2001 From: Peder Bergebakken Sundt Date: Fri, 21 Mar 2025 19:14:52 +0100 Subject: [PATCH] asd --- .gitignore | 1 + clean.sh | 96 ++++++++++++ justfile | 436 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 533 insertions(+) create mode 100644 .gitignore create mode 100755 clean.sh create mode 100644 justfile diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..72e8ffc --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +* diff --git a/clean.sh b/clean.sh new file mode 100755 index 0000000..cb37e75 --- /dev/null +++ b/clean.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash + +# TODO: list worktree where a --force-with-lease would fail, those are often changed from a different host + +test -e master/.git || { + >&2 echo "ERROR: not right cwd?" + exit 1 +} + +if test -t 0 && test -t 1 && command -v gum >/dev/null; then + >&2 echo gumming... + cleanlist="$(./clean.sh)" || exit 1 + gum choose --no-limit --height="$(($(wc -l <<<"$cleanlist") + 5))" <<<"$cleanlist" | bash -x - + exit 0 +fi + +( + cd master + # TODO: assert upstream? + git fetch upstream master staging staging-next python-updates +) + + +porcelain2jsonl() { + uniq | ( + printf "[" + while read key data; do + if test -z "$key"; then + printf "]\n[" + else + printf '{"%s": "%s"}, ' "$key" "$data" + fi + done + echo "]" + ) | sed -e 's/, ]/]/g' | grep -v '^\[\]$' | jq add -c +} + +worktrees="$( cd master; git worktree list --porcelain | porcelain2jsonl )" +#cat <<<"$worktrees" + +#exit + +( + cd master + ( + git branch --merged upstream/master + git branch --merged upstream/staging + git branch --merged upstream/staging-next + git branch --merged upstream/python-updates + git branch --merged upstream/release-24.05 + # git branch --merged upstream/release-23.11 + ) | sort -u +) | +while IFS= read line; do + branch="$(echo "$line" | cut -c3-)" + if test "$branch" = "master" \ + || test "$branch" = "staging" \ + || test "$branch" = "staging-next" \ + || test "$branch" = "python-updates" \ + || test "$branch" = "release-24.05" \ + || test "$branch" = "release-23.11" + then + continue + fi + #jq <<<"$worktrees" '.branch' + worktree="$( jq <<<"$worktrees" 'select(.branch == "refs/heads/'"$branch"'")' -c | head -n1 )" + + #echo "$line" + #echo "$branch" + #echo "$worktree" + + if test -n "$worktree"; then + path="$(jq <<<"$worktree" .worktree -r )" + if test -d "$path"; then + + ( cd "$path" ; + test -n "$(git diff --staged)" || + test -n "$(git diff)" || + test -n "$(git ls-files --others --exclude-standard | grep -Ev '^(update-executables\.txt|results|build\.nix|(asd|do|foo|test|spismeg|packages)[2-9]?.(sh|txt|json)|eval.json|[^/]*\.log)$')" + ) &>/dev/null && continue + + if test "$(basename "$path")" != "tmp" \ + && test "$(basename "$path")" != "scratch" \ + ;then + echo rm -rf "$path" + fi + fi + fi + + echo "( cd master && git worktree prune && git branch -D \"$branch\" )" + +done + +#echo "( cd master && git worktree prune && git prune )" # slow +echo "( cd master && git worktree prune )" +echo "rmdir */ --ignore-fail-on-non-empty" diff --git a/justfile b/justfile new file mode 100644 index 0000000..f179304 --- /dev/null +++ b/justfile @@ -0,0 +1,436 @@ +#!/usr/bin/env just --justfile +# makes variables accesible as $1 $2 $@, useful for escaping variadics + +set positional-arguments := true + +export invokedir := invocation_directory() +export epoch := `date +%s` +export GUM_FILTER_HEIGHT := "15" +export GIT := `command -v git-wait >/dev/null && echo "git-wait" || echo "git"` + +# required programs: git nix nom gum fzf tee nix-update +#export NIXPKGS_ALLOW_UNFREE := "1" +#export NIXPKGS_ALLOW_INSECURE := "1" +#export NIXPKGS_ALLOW_BROKEN := "1" + +@_default: + cd "$invokedir"; just "$(gum filter $(just --summary --unsorted))" + +# === info helpers === + +@list-packages +$cut_args="-c1-": _packages_json + cachefile="${XDG_RUNTIME_DIR:-"${TMPDIR:-/tmp}"}"/packages.json.tsv; \ + if test -s "$cachefile" && test "$(stat -c %Y packages.json )" -le "$(stat -c %Y "$cachefile" )"; then \ + cat "$cachefile" | cut "$@" ; \ + else \ + just _list_packages | tee "$cachefile" | cut "$@"; \ + fi +@_list_packages: _packages_json + #jq &2 echo "ripgrep args missing!"; false; } + just list-packages | grep --fixed-strings "$( \ + cd "$invokedir"; set -x; rg "$@" pkgs/ -tnix -l | sed 's/\(.*\)/\t\1\t/' | sort | head -n 2500 \ + | grep -v pkgs/development/haskell-modules/hackage-packages.nix \ + | grep -v pkgs/top-level/ \ + )" + +@list-dirty-packages: + # TODO: switch to 'git diff HEAD --numstat'? + test -n "$(cd "$invokedir"; $GIT ls-files --modified)" || ( >&2 echo "No unstaged dirty files found!"; false ) + just list-packages | grep --fixed-strings "$( \ + cd "$invokedir"; $GIT ls-files --modified | sed 's/\(.*\)/\t\1\t/' \ + | grep -v pkgs/development/haskell-modules/hackage-packages.nix \ + | grep -v pkgs/top-level/ \ + )" + +@list-touched-packages-since $rev=`cd "$invokedir"; just _a_commit`: _packages_json + test -n "$rev" || ( >&2 echo "you must pick a revision to compare against!"; false ) + just list-packages | grep --fixed-strings "$( \ + cd "$invokedir"; $GIT diff "$rev" --numstat | cut -f3 \ + | grep -v pkgs/development/haskell-modules/hackage-packages.nix \ + | grep -v pkgs/top-level/ \ + )" + +@list-unmerged-packages: + -cd "$invokedir"; $GIT fetch upstream + cd "$invokedir"; $GIT log HEAD --not --remotes --oneline | cut -d' ' -f2- | cut -d: -f1 | grep -vE '(^treewide|[, ])' | sort -u + +# === build helpers === + +# this also needs 'ripgrep_args' somehow... +#@build-ripgrepped-packages $attr_prefix="" $attr_suffix="" +$extra_args="": +# [[ -n "$extra_args" ]] && export _JUST_NIX_INSTANTIATE_ARGS="$(shift; shift; printf " %q" "$@")"; \ +# cd "$invokedir"; just list-dirty-packages | cut -f1 | xargs printf "$attr_prefix"'%s'"$attr_suffix " | xargs just build-packages + +@build-dirty-packages $attr_prefix="" $attr_suffix="" +$extra_args="": + [[ -n "$extra_args" ]] && export _JUST_NIX_INSTANTIATE_ARGS="$(shift; shift; printf " %q" "$@")"; \ + cd "$invokedir"; just list-dirty-packages | cut -f1 | xargs printf "$attr_prefix"'%s'"$attr_suffix " | xargs just build-packages + +@instantiate-dirty-packages $attr_prefix="" $attr_suffix="" +$extra_args="": + [[ -n "$extra_args" ]] && export _JUST_NIX_INSTANTIATE_ARGS="$(shift; shift; printf " %q" "$@")"; \ + cd "$invokedir"; just list-dirty-packages | cut -f1 | xargs printf "$attr_prefix"'%s'"$attr_suffix " | xargs just instantiate-packages + +@build-unmerged-packages $attr_prefix="" $attr_suffix="" +$extra_args="": + [[ -n "$extra_args" ]] && export _JUST_NIX_INSTANTIATE_ARGS="$(shift; shift; printf " %q" "$@")"; \ + cd "$invokedir"; just list-unmerged-packages | xargs printf "$attr_prefix"'%s'"$attr_suffix " | xargs just build-packages + +@instantiate-unmerged-packages $attr_prefix="" $attr_suffix="" +$extra_args="": + [[ -n "$extra_args" ]] && export _JUST_NIX_INSTANTIATE_ARGS="$(shift; shift; printf " %q" "$@")"; \ + cd "$invokedir"; just list-unmerged-packages | xargs printf "$attr_prefix"'%s'"$attr_suffix " | xargs just instantiate-packages + +@build-touched-packages-since $attr_prefix="" $attr_suffix="" +$extra_args="": + [[ -n "$extra_args" ]] && export _JUST_NIX_INSTANTIATE_ARGS="$(shift; shift; printf " %q" "$@")"; \ + cd "$invokedir"; just list-touched-packages-since | cut -f1 | xargs printf "$attr_prefix"'%s'"$attr_suffix " | xargs just build-packages + +@instantiate-touched-packages-since $attr_prefix="" $attr_suffix="" +$extra_args="": + [[ -n "$extra_args" ]] && export _JUST_NIX_INSTANTIATE_ARGS="$(shift; shift; printf " %q" "$@")"; \ + cd "$invokedir"; just list-touched-packages-since | cut -f1 | xargs printf "$attr_prefix"'%s'"$attr_suffix " | xargs just instantiate-packages + +# + +@instantiate-packages +$attrpaths="": + # slower, but resitant to bad attrpaths + -test -n "${attrpaths:-}" || set -- $(just _some_packages); \ + cd "$invokedir"; \ + if [[ -v _INSTANTIATE_CACHE && -d "${_INSTANTIATE_CACHE:-}" ]]; then \ + mkdir -p "$_INSTANTIATE_CACHE"/root; \ + printf -- "%s\n" "$@" | grep . | NIXPKGS_ALLOW_UNFREE=1 xe -j0 -s 'C="$_INSTANTIATE_CACHE/$1"; test -s "$C" && cat "$C" || ( nix-instantiate . -A "$1" '"${_JUST_NIX_INSTANTIATE_ARGS:-}"' --add-root "$_INSTANTIATE_CACHE/roots/$1" | tee "$C")'; \ + else \ + printf -- "%s\n" "$@" | grep . | NIXPKGS_ALLOW_UNFREE=1 xe -j0 -s 'nix-instantiate . -A "$1" '"${_JUST_NIX_INSTANTIATE_ARGS:-}"''; \ + fi + # TODO: filter the "warning: you did not specify '--add-root'; the result might be removed by the garbage collector" + +# _JUST_NIX_BUILD_ARGS='--check' +# _JUST_NIX_BUILD_ARGS='-j0 --builders "@/etc/nix/machines; ssh://pederbs@heid.idi.ntnu.no x86_64-linux - 24 5 big-parallel"' +# _JUST_NIX_INSTANTIATE_ARGS='--system darwin' +@build-packages +$attrpaths: + test -n "${attrpaths:-}" || set -- $(just _some_packages); \ + cd "$invokedir"; just _build_packages results "$@" + +@_build_packages $outdir +$attrpaths: + # hint: supports _JUST_NIX_BUILD_ARGS + # hint: supports _JUST_NIX_INSTANTIATE_ARGS + test -z "${_JUST_NIX_BUILD_ARGS:-}" || >&2 echo "_JUST_NIX_BUILD_ARGS = $_JUST_NIX_BUILD_ARGS" + test -z "${_JUST_NIX_INSTANTIATE_ARGS:-}" || >&2 echo "_JUST_NIX_INSTANTIATE_ARGS = $_JUST_NIX_INSTANTIATE_ARGS" + # -test -d "$invokedir"/"$outdir" && rm -v "$invokedir"/"$outdir"/* + -test -d "$invokedir"/"$outdir" && { \ + fd . "$invokedir"/"$outdir" --type l -X rm -v; \ + fd \\\.log$ "$invokedir"/"$outdir" --type f -X rm -v; \ + } + mkdir -p "$invokedir"/"$outdir" + cd "$invokedir"; \ + export _INSTANTIATE_CACHE=$(mktemp -d); \ + export NOTFOUNDDIR="$(mktemp -d)"; touch "$NOTFOUNDDIR"/not-found; \ + shift; \ + just instantiate-packages "$@" | tee >(cat>&2) | eval xargs nom-build --keep-going --no-out-link ${_JUST_NIX_BUILD_ARGS:-}; \ + for attrpath in "$@"; do ( \ + NIXPKGS_ALLOW_UNFREE=1 nix-build . -A "$attrpath" ${_JUST_NIX_INSTANTIATE_ARGS:-} -j0 --option builders "" --option substitute false -o "$outdir"/"$attrpath" 2>/dev/null >/dev/null; \ + if test -L "$outdir"/"$attrpath"; then \ + nix log $(readlink "$invokedir"/"$outdir"/"$attrpath") > "$outdir"/"$attrpath".log 2>/dev/null; \ + else \ + if [[ -z "$(just instantiate-packages "$attrpath")" ]]; then \ + ln -s "$NOTFOUNDDIR"/not-found "$outdir"/"$attrpath"; \ + else \ + ln -s /build-failure-"$attrpath" "$outdir"/"$attrpath"; \ + nix log ".#$attrpath" ${_JUST_NIX_INSTANTIATE_ARGS:-} > "$outdir"/"$attrpath".log 2>/dev/null || true; \ + fi \ + fi \ + ) & \ + while test "$(jobs -p | wc -l)" -ge 8; do wait < <(jobs -p); done; \ + done; wait + echo -e "#!/usr/bin/env bash\ntest -f flake.nix || cd ..; env _JUST_NIX_INSTANTIATE_ARGS=\"\$*\" just _build_packages \"$outdir\"" $attrpaths > "$invokedir"/"$outdir"/_rerun.sh + chmod +x "$invokedir"/"$outdir"/_rerun.sh + cd "$invokedir"; fd . "$outdir" -l + +@build-pr-packages $pr=`just _a_pr` $merge=`gum choose --header="which refs/pull//xxx ?" merge head` $attrpaths=`just _some_packages` $args=`gum choose --no-limit -- -j0 "--system "{aarch64,x86_64}-{darwin,linux} --rebuild`: + -printf "github:nixos/nixpkgs/refs/pull/"$(just _sanitize_pr_url "$pr")"/$merge#%s" $attrpaths | xargs echo + nom build $args + -(printf "github:nixos/nixpkgs/refs/pull/"$(just _sanitize_pr_url "$pr")"/$merge#%s" $attrpaths | xargs nom build $args) || true + -printf "github:nixos/nixpkgs/refs/pull/"$(just _sanitize_pr_url "$pr")"/$merge#%s" $attrpaths | xargs echo + nom build $args + +# === commit helpers === + +#@commit-with-pkgname: +# cd "$invokedir"; test -n "$(git diff HEAD --name-only --staged)" || { >&2 echo "No files are staged!"; false; } +# cd "$invokedir"; $GIT -c commit.template=<(echo "$(just list-packages | grep --fixed-strings "$(git diff HEAD --name-only --staged)" | cut -f1 | head -n1): ") commit + +@commit-dirty-packages +$message=`gum input --placeholder="commit message, (attrpath: this message)"`: + test -n "$message" + (cd "$invokedir"; just list-dirty-packages) | \ + while read attrpath position row; do \ + test -f "$invokedir/$position" || continue; \ + ( cd "$invokedir"; $GIT diff -s --exit-code "$position" || ( set -x; $GIT add "$position" ; $GIT commit -m "$attrpath: $*" ) ); \ + done + +@fixup-commit: + # TODO: list commits first + cd "$invokedir"; test -n "$($GIT diff --name-only --staged)" || { \ + $GIT ls-files --modified --others --exclude-standard | grep -v ^results- | fzf --layout=reverse --multi --height 15 | xe $GIT add --patch; \ + } + cd "$invokedir"; test -n "$($GIT diff --name-only --staged)" || { >&2 echo Nothing is staged!; false; } + cd "$invokedir"; just fixup-commit-staged + +@fixup-commit-staged commit=`cd "$invokedir"; just _a_commit`: + test -n "{{ commit }}" # just exit if no selection was made + cd "$invokedir"; test -n "$($GIT diff --name-only --staged)" || { >&2 echo Nothing is staged!; false; } + cd "$invokedir"; $GIT diff --staged + echo; gum format "# ========= into ========="; echo + cd "$invokedir"; $GIT log {{ commit }} -n1 + @gum confirm + cd "$invokedir"; $GIT commit --fixup={{ commit }} + cd "$invokedir"; GIT_SEQUENCE_EDITOR=true $GIT rebase -i --autostash --autosquash "$($GIT log -n1 --pretty=%P {{ commit }})" + +rebase-commits commit=`cd "$invokedir"; just _a_commit`: + cd "$invokedir"; $GIT rebase -i --autostash --autosquash "$($GIT log -n1 --pretty=%P {{ commit }})" + +# === speed worktrees === + +bump +$packages=`just _some_packages`: + cd "$(just _new_worktree bump "$(printf "%s\n" $packages | head -n1 | rev | cut -d. -f1 | rev)")"; just _bump $packages; NIXPKGS_ALLOW_UNFREE=1 $SHELL + +_bump +$packages: + test -e "$invokedir"/.git + -cd "$invokedir"; printf "%s.src\n" "$@" | xargs just _build_packages results-src-old # TODO: unpack? + cd "$invokedir"; for package in "$@"; do \ + if nix eval -f . "$package".passthru.updateScript >&/dev/null; then \ + HEAD=$($GIT rev-parse HEAD); \ + NIXPKGS_ALLOW_UNFREE=1 NIXPKGS_ALLOW_BROKEN=1 NIXPKGS_ALLOW_INSECURE=1 nix-update "$package" --use-update-script --update-script-args "--argstr skip-prompt true" --commit; \ + if [[ $($GIT rev-parse HEAD) = "$HEAD" ]]; then \ + echo "no commit found, trying without the updateScript..."; \ + $GIT restore .; \ + NIXPKGS_ALLOW_UNFREE=1 NIXPKGS_ALLOW_BROKEN=1 NIXPKGS_ALLOW_INSECURE=1 nix-update "$package" --commit || true; \ + fi \ + else \ + NIXPKGS_ALLOW_UNFREE=1 NIXPKGS_ALLOW_BROKEN=1 NIXPKGS_ALLOW_INSECURE=1 nix-update "$package" --commit || true; \ + fi \ + done + -cd "$invokedir"; printf "%s.src\n" "$@" | xargs just _build_packages results-src-new # TODO: unpack? + -cd "$invokedir"; just _build_packages results "$@" + @# TODO: filter non-existing tests: + -cd "$invokedir"; printf "%s.tests\n" "$@" | env _JUST_NIX_BUILD_ARGS="-j1" xargs just _build_packages results-tests + -cd "$invokedir"; printf "HEAD^%s " $(seq 0 $(($#-1))) | xe $GIT show + -cd "$invokedir"; fd . -l results results-tests --max-depth 1 + # -cd "$invokedir"; test -L result && fd . result --type x --color=always | tee update-executables.txt + # -cd "$invokedir"; $GIT show --name-only --pretty="" | xe statix fix + cd "$invokedir"; $GIT diff + printf "delta results-src-{old,new}/%s/.\n" "$@" + +#fix package=`just _a_package`: + +# ./mk-fix.sh "{{package}}" +@fix $package=`just _a_package`: + cd "$(just _new_worktree fix "$(echo "$package" | rev | cut -d. -f1 | rev)")"; just _fix "$package" && $SHELL + +@_fix $package: + test -e "$invokedir"/.git + cd "$invokedir"; nix-build . -A "$package".src --out-link result-src + -cd "$invokedir"; test -d $(readlink result-src) && { mkdir -p result-src-writeable; rsync -rxL result-src/ result-src-writeable/ ; chmod -R +w result-src-writeable ;} + -cd "$invokedir"; test -f $(readlink result-src) && { \ + mkdir result-src-writeable; \ + atool $(readlink result-src) --extract-to=result-src-writeable; \ + if test $(ls result-src-writeable | wc -l ) -eq 1 && test -d result-src-writeable/* ; then \ + mv result-src-writeable{,-old}; \ + mv result-src-writeable-old/* result-src-writeable; \ + rmdir result-src-writeable-old; \ + fi \ + } + -test -d "$invokedir"/result-src-writeable; cd "$invokedir"/result-src-writeable; $GIT init ; $GIT add . ; $GIT commit -m "Base" >/dev/null + { \ + echo '#!/usr/bin/env -S nom build -f'; \ + echo 'let pkgs = import ./. {}; in pkgs.{{ package }}.overrideAttrs { src = ./result-src-writeable; }'; \ + } > "$invokedir"/build.nix + chmod +x "$invokedir"/build.nix + cd "$invokedir"; NIXPKGS_ALLOW_UNFREE=1 just build-packages "$package" || true + echo "# $package" + echo ' ./build.nix' + echo ' ./results/_rerun.sh' + +@pr $number=`just _a_pr`: + if test -d prs/pr-"$(just _sanitize_pr_url "$number")"-* ; then \ + cd prs/pr-"$(just _sanitize_pr_url "$number")"-* && $SHELL ; \ + else \ + just _pr "$(just _sanitize_pr_url "$number")" ; \ + fi + +_pr $number: + cd "$(just _new_worktree prs pr-{{ number }} "upstream/master" "")" ; gh pr checkout {{ number }} && $GIT branch -D prs-$(basename "$PWD") && $SHELL + +@pr-comment-diff: + cd "$invokedir"; just _pr_post_diff comment + +@pr-review-diff: + cd "$invokedir"; just _pr_post_diff "review $(gum choose --ordered -- --comment --request-changes --approve)" + +@_pr_post_diff mode: + test "prs" = "$(cd "$invokedir" ; $GIT rev-parse --show-toplevel | rev | cut -d/ -f2 | rev)" + test -n "$(cd "$invokedir"; $GIT diff --staged)" || { echo no changes staged; cd "$invokedir"; $GIT add --patch; true; } + test -n "$(cd "$invokedir"; $GIT diff --staged)" || { echo no changes staged; false; } + cd "$invokedir"; \ + NUMBER="$($GIT rev-parse --show-toplevel | rev | cut -d/ -f1 | rev | cut -d- -f2)"; \ + FNAME="$(mktemp --suffix=.md)"; \ + echo "NUMBER=$NUMBER" "FNAME=$FNAME"; \ + >>"$FNAME" echo; \ + >>"$FNAME" echo '
'; \ + >>"$FNAME" echo ':clipboard: proposed patch'; \ + >>"$FNAME" echo; \ + >>"$FNAME" echo '```patch'; \ + >>"$FNAME" $GIT diff --staged; \ + >>"$FNAME" echo '```'; \ + >>"$FNAME" echo; \ + >>"$FNAME" echo '
'; \ + >>"$FNAME" echo; \ + >>"$FNAME" echo '_How to apply (pick one):_'; \ + >>"$FNAME" echo; \ + >>"$FNAME" echo 'a. `curl -L "'"$($GIT diff --staged | gh gist create -f pr-$NUMBER.patch -)"'/raw" | $GIT apply`'; \ + >>"$FNAME" echo 'b. copy the patch above, run `$GIT apply`, paste (typically `ctrl-shift-v`), press enter, then end the input with `ctrl-d`'; \ + "$EDITOR" "$FNAME"; \ + test -s "$FNAME" && bat --language markdown --style plain --pager never "$FNAME" && gum confirm && gh pr {{ mode }} "$NUMBER" --body-file "$FNAME"; \ + rm -v "$FNAME" + +# TODO: clean + +@remote-branch $remote_ref=`just _a_remote_branch origin | cut -d/ -f2-`: + cd master; $GIT worktree add --track ../"$(cut <<<"$remote_ref" -d- -f1)"/"$(cut <<<"$remote_ref" -d- -f2-)" -b "$remote_ref" origin/$remote_ref + cd "$(cut <<<"$remote_ref" -d- -f1)"/"$(cut <<<"$remote_ref" -d- -f2-)"; $SHELL + +# === worktrees === +# TODO: onto release-xx.yy doesn't work + +# TODO: python-updates +@new-worktree *args: + cd "$(just _new_worktree "$@")"; $SHELL + +@_new_worktree $type=`gum choose feat fix init bump doc migrate` $name=`gum input --placeholder="Branch name?"` $base=`just _a_upstream_release_branch "Base?"` $onto=`just _a_upstream_release_branch "Onto?" ""`: + test -n "$type" + test -n "$name" + just _mk_worktree "$type-$(printf "%s" "$name" | tr '[:space:]/' -- | tr -s -)-{{ epoch }}" "$type/$(printf "%s" "$name" | tr '[:space:]/' -- | tr -s -)-{{ epoch }}" "$base" "$onto" 1>&2 + echo "$type/$(printf "%s" "$name" | tr '[:space:]/' -- | tr -s -)-{{ epoch }}" + +@_mk_worktree $name $dir $base="upstream/master" $onto="": + test -n "$name" + test -n "$dir" + cd master; $GIT worktree prune + cd master; $GIT fetch "$(cut -d/ -f1 <<<"$base")" "$(cut -d/ -f2 <<<"$base")" + cd master; $GIT branch "$name" "$(cut -d/ -f2 <<<"$base")" --no-track + cd master; $GIT worktree add ../"$dir" "$name" + cd "$dir"; $GIT pull "$(cut -d/ -f1 <<<"$base")" "$(cut -d/ -f2 <<<"$base")" + test -z "$onto" || { cd "$dir"; just rebase-onto HEAD "$onto"; } + +@pop-commits-to-new-worktree $commits=`cd "$invokedir"; just _some_commits`: + test -n "$commits" || { >&2 echo "no commits were selected!"; false;} + # we make a nasty assumption here: the last commit passed in is also the topologically oldest one in the git history + # we also assume the revs in the interactive rebase are just as short or longer as those returned by 'just _some_commits' + new_worktree="$(just _new_worktree "$(gum choose feat fix init bump doc migrate)" "$(gum input --placeholder="Branch name?" --value="$(cd "$invokedir"; git show --oneline --no-patch "$(head -n1 <<<"$commits")" | cut -d' ' -f2- | cut -d: -f1 | rev | cut -d. -f1 | rev)")" )" && \ + (cd "$new_worktree"; tac <<<"$commits" | xargs git cherry-pick) \ + && { \ + (cd "$invokedir"; EDITOR="sed -i $(xargs <<<"$commits" printf " -e 's/^pick %s/#/g'")" git rebase "$(tail -n1 <<<"$commits")"^^ -i) \ + cd "$new_worktree" && git log -n "$(wc -l <<<"$commits" )" && $SHELL; \ + true; \ + } \ + || { \ + >&2 echo "The commits have not been popped."; \ + cd "$new_worktree" && git status; \ + >&2 echo "The commits have not been popped."; \ + $SHELL; \ + } + +@_rebase_onto $onto: + test -n "$onto" + cd "$invokedir"; $GIT fetch upstream "$onto" ; $GIT rebase --onto upstream/"$onto"...HEAD upstream/master --autostash + +@rebase-onto $base=`just _a_upstream_release_branch "Base?" HEAD` $onto=`just _a_upstream_release_branch "Onto?"`: + cd "$invokedir"; test "$base" = HEAD || { remote="$(cut -d/ -f1 <<<"$base")"; branch="$(cut -d/ -f2- <<<"$base")"; set -x; $GIT fetch "${remote:-upstream}" "$branch"; } + cd "$invokedir"; test "$onto" = HEAD || { remote="$(cut -d/ -f1 <<<"$onto")"; branch="$(cut -d/ -f2- <<<"$onto")"; set -x; $GIT fetch "${remote:-upstream}" "$branch"; } + #cd "$invokedir"; set -x; $GIT rebase --onto upstream/"$onto"...upstream/"$base" upstream/"$base" --autostash + +# fix periodic merge conflicts + +fix-periodic-merge-conflict $from=`just _a_upstream_release_branch "From?"` $to=`just _a_upstream_release_branch "To?"`: + cd "$invokedir"; $GIT fetch upstream "$from" + cd "$invokedir"; $GIT fetch upstream "$to" + cd "$invokedir"; $GIT worktree add -b fix-merge-conflict-"$from"-to-"$to"-{{ epoch }} ../fix/merge-conflict-"$from"-to-"$to"-{{ epoch }} upstream/"$to" + cd fix/merge-conflict-"$from"-to-"$to"-{{ epoch }}; $GIT merge upstream/"$from" || true + cd fix/merge-conflict-"$from"-to-"$to"-{{ epoch }}; $GIT status + @echo "Cheatsheet:" + @echo " $GIT ls-files --modified | sort -u | xe -N0 $GIT reset" + @echo " $GIT commit" + @echo " $GIT push upstream HEAD:$to" + cd fix/merge-conflict-"$from"-to-"$to"-{{ epoch }}; $SHELL + +# === setup === + +setup: + test -e master/.git || $GIT clone "git@github.com:$(gum input --header "$GIT clone git@github.com:< username >/nixpkgs.git" --value="$(whoami)")/nixpkgs.git" master + test -e master/.git + cd master; $GIT remote get-url upstream >/dev/null || $GIT remote add upstream "https://github.com/NixOS/nixpkgs.git" + test -d upstream/staging || ( cd master && $GIT worktree add ../upstream/staging -B staging upstream/staging ) + test -d upstream/staging-next || ( cd master && $GIT worktree add ../upstream/staging-next -B staging-next upstream/staging-next ) + test -d upstream/staging-24.11 || ( cd master && $GIT worktree add ../upstream/staging-24.11 -B staging-24.11 upstream/staging-24.11 ) + test -d upstream/staging-next-24.11 || ( cd master && $GIT worktree add ../upstream/staging-next-24.11 -B staging-next-24.11 upstream/staging-next-24.11 ) + +# TODO: test +#upstreams-fetch: +# cd master; $GIT fetch upstream master +# cd master; $GIT fetch upstream staging +# cd master; $GIT fetch upstream staging-next +# cd master; $GIT fetch upstream release-24.05 +# cd master; $GIT fetch upstream staging-24.05 +# cd master; $GIT fetch upstream release-23.11 +# cd master; $GIT fetch upstream staging-23.11 +# cd master; $GIT fetch upstream python-updates +# === helpers === +# TODO stat the file and redo if too old, or tie it to the master checkout commit + +# TODO: dump it in /tmp? +@_packages_json: + ( \ + test -s packages.json \ + && test $(( "$epoch" - "$(stat -c %Y packages.json )" )) -lt $(( 60*60*24*7 )) \ + ) || { set -x; \ + (cd master; git diff --exit-code --quiet) || { \ + >&2 echo "ERROR: $(realpath master) is dirty!"; \ + false; \ + }; \ + (cd master; git pull upstream master); \ + time nix-env --extra-experimental-features no-url-literals --option system x86_64-linux -f ./master -qaP --json --meta --show-trace --no-allow-import-from-derivation --arg config '{ allowAliases = false; }' > packages.json; \ + } + +@_a_commit: + # TODO: check if in a git repo + cd "$invokedir"; $GIT log --oneline -n800 | gum filter --placeholder "Pick commit..." --height 10 | cut -d' ' -f1 + +@_some_commits: + # TODO: check if in a git repo + #cd "$invokedir"; $GIT log --oneline -n800 | gum filter --placeholder "Pick commit..." --height 10 --no-limit | cut -d' ' -f1 + cd "$invokedir"; $GIT log --oneline -n800 | fzf --layout=reverse --multi --height 15 | cut -d' ' -f1 + +@_a_package: _packages_json + just list-packages | cut -f1 | fzf --layout=reverse --height 15 | grep . + +@_some_packages: _packages_json + just list-packages | cut -f1 | fzf --layout=reverse --multi | grep . + +@_a_pr: + cd master; gh pr list --limit 1000 --json 'number,title' --state open | jq '.[]|"\(.number) - \(.title)"' -r | gum filter --placeholder "Pick PR..." --height 10 | cut -d' ' -f1 + +@_a_upstream_release_branch $header *$extra_branches: + test -n "${prefix:-}" || prefix="upstream/"; \ + shift; gum choose --header="$header" "$@" "${prefix}"master "${prefix}"staging "${prefix}"staging-next "${prefix}"python-updates "${prefix}"release-24.11 "${prefix}"staging-24.11 "${prefix}"staging-next-24.11 + # --ordered + +@_sanitize_pr_url $number: + echo "$number" | tr '[:upper:]' '[:lower:]' | sed -Ee 's|https?://github.com/nixos/nixpkgs/pull/([0-9]+).*|\1|' + +@_a_remote_branch $remote="origin": + cd master; $GIT fetch "$remote" + cd master; $GIT for-each-ref --sort=committerdate refs/remotes --format='%(committerdate:short) %(refname:short)' | sort -r | grep " $remote/" | gum filter --height=15 --no-sort | cut -d' ' -f2- + +@_fmt: + just --unstable --fmt + #sd "\n+(@_[a-zA-Z_-]+:)" "\n" justfile