diff options
Diffstat (limited to 'desktop/modules')
| -rw-r--r-- | desktop/modules/git/GitPage.qml | 27 | ||||
| -rw-r--r-- | desktop/modules/git/README.md | 8 | ||||
| -rwxr-xr-x | desktop/modules/git/gitcfg | 26 | ||||
| -rwxr-xr-x | desktop/modules/git/test-gitcfg.sh | 19 |
4 files changed, 71 insertions, 9 deletions
diff --git a/desktop/modules/git/GitPage.qml b/desktop/modules/git/GitPage.qml index c1bffb5..c91f050 100644 --- a/desktop/modules/git/GitPage.qml +++ b/desktop/modules/git/GitPage.qml @@ -31,6 +31,7 @@ Column { // write because the value did not change. property var errors: ({}) property bool busy: false + property string loadError: "" // The module, which outlives the page: a write in flight finishes even if // the page is closed. Not named `gm`, the module's id (it would bind to itself). required property QtObject owner @@ -58,7 +59,10 @@ Column { function load() { page.run([page.ctl, "list"], out => { - page.entries = G.parse(out); + // gitcfg reports a file git cannot parse as one error= line; + // without it the page would draw an empty config. + page.loadError = out.startsWith("error=") ? out.slice(6).trim() : ""; + page.entries = page.loadError ? [] : G.parse(out); page.busy = false; }); } @@ -85,6 +89,15 @@ Column { onClicked: Quickshell.execDetached(["sublime_text", Quickshell.env("HOME") + "/.gitconfig"]) } + Text { + width: parent.width + visible: page.loadError !== "" + text: page.loadError + wrapMode: Text.Wrap + color: Theme.red + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + } + Repeater { model: page.entries @@ -200,6 +213,12 @@ Column { selectByMouse: true color: Theme.text font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 } + // Typed text is cleared first, as a row's edit is undone; + // an empty field lets Escape through, to leave the page. + Keys.onEscapePressed: event => { + if (text !== "") text = ""; + else event.accepted = false; + } Text { anchors.verticalCenter: parent.verticalCenter visible: parent.text === "" @@ -223,6 +242,12 @@ Column { selectByMouse: true color: Theme.text font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 } + // Typed text is cleared first, as a row's edit is undone; + // an empty field lets Escape through, to leave the page. + Keys.onEscapePressed: event => { + if (text !== "") text = ""; + else event.accepted = false; + } onAccepted: if (addBtn.enabled) addBtn.clicked() Text { anchors.verticalCenter: parent.verticalCenter diff --git a/desktop/modules/git/README.md b/desktop/modules/git/README.md index 57c8965..e5a4370 100644 --- a/desktop/modules/git/README.md +++ b/desktop/modules/git/README.md @@ -13,7 +13,13 @@ its comments and layout. `--fixed-value` makes a write touch one value of a multi-valued key. `set` and `unset` first check the old value is still there, because `--replace-all` with no match appends instead of failing, and the file may have changed in the editor. After every write the page reads -the list again; a failure shows git's reason under its row. +the list again; a failure shows git's reason under its row. A value held +twice by the same key is refused, since git would merge both rows into one +on edit and cannot remove just one; fix those in the editor. A file git +cannot parse shows git's error instead of an empty list. + +Every reload rebuilds the rows, so an edit typed into one row and not +saved with Enter is lost when another row is saved or removed. Checks: `./test-gitcfg.sh` (a throwaway `GIT_CONFIG_GLOBAL`) and `node desktop/modules/git/test_git.js`. diff --git a/desktop/modules/git/gitcfg b/desktop/modules/git/gitcfg index df955bf..a5641f7 100755 --- a/desktop/modules/git/gitcfg +++ b/desktop/modules/git/gitcfg @@ -13,7 +13,7 @@ # # The global git config, for the drawer's git page. # -# gitcfg list git config --global -z --list +# gitcfg list git config --global -z --list, or error=<why> # gitcfg set <key> <old> <new> replace the one value equal to <old> # gitcfg add <key> <value> add a value # gitcfg unset <key> <value> remove the one value equal to <value> @@ -39,16 +39,28 @@ g() { err="$(git config --global "$@" 2>&1)" || { printf '%s\n' "${err:-git config failed}"; exit 1; } } -# --replace-all with no value equal to <old> appends <new> and succeeds, and -# --unset of a missing value fails silently, so both check first: the file -# may have changed in the editor since the page read it. +# Exactly one value equal to <value>, or refuse. --replace-all with no match +# appends <new> and succeeds, and --unset of a missing value fails silently: +# the file may have changed in the editor since the page read it. With two +# equal values --replace-all merges both rows into one and --unset fails, so a +# duplicate is left to the editor. has() { - git config --global --fixed-value --get-all -- "$1" "$2" >/dev/null \ - || { printf '%s no longer holds that value\n' "$1"; exit 1; } + local n + n="$(git config --global -z --fixed-value --get-all -- "$1" "$2" | tr -cd '\0' | wc -c)" + case "$n" in + 1) ;; + 0) printf '%s no longer holds that value\n' "$1"; exit 1 ;; + *) printf '%s holds that value twice or more; use the editor\n' "$1"; exit 1 ;; + esac } case "${1:-}" in - list) [[ $# -eq 1 ]] || usage; exec git config --global -z --list ;; + # A file git cannot parse prints nothing on stdout, which would read as + # an empty config, so its error comes out as one error= line instead. + list) [[ $# -eq 1 ]] || usage + err="$(git config --global --list 2>&1 >/dev/null)" \ + || { printf 'error=%s\n' "${err:-git config failed}"; exit 1; } + exec git config --global -z --list ;; set) [[ $# -eq 4 ]] || usage; has "$2" "$3"; g --fixed-value --replace-all -- "$2" "$4" "$3" ;; add) [[ $# -eq 3 ]] || usage; g --add -- "$2" "$3" ;; unset) [[ $# -eq 3 ]] || usage; has "$2" "$3"; g --fixed-value --unset -- "$2" "$3" ;; diff --git a/desktop/modules/git/test-gitcfg.sh b/desktop/modules/git/test-gitcfg.sh index 328a575..6e1b32c 100755 --- a/desktop/modules/git/test-gitcfg.sh +++ b/desktop/modules/git/test-gitcfg.sh @@ -71,6 +71,25 @@ check "add bad key: git's error" 1 "$([[ "$out" == *section* ]] && echo 1)" check "comment kept" 1 "$(grep -c '^# keep me$' "$GIT_CONFIG_GLOBAL")" +# A value held twice: --fixed-value would replace or trip on both, so gitcfg +# refuses rather than silently merging two rows into one. +printf '[d]\n\tv = a\n\tv = a\n\tv = b\n' >> "$GIT_CONFIG_GLOBAL" +out="$("$ctl" set d.v a c)"; rc=$? +check "set duplicate: rc" 1 "$rc" +check "set duplicate: untouched" "a,a,b" "$(vals d.v)" +out="$("$ctl" unset d.v a)"; rc=$? +check "unset duplicate: rc" 1 "$rc" +check "unset duplicate: says why" 1 "$([[ "$out" == *twice* ]] && echo 1)" + +# A file git cannot parse lists nothing on stdout without this, which the +# page would draw as an empty config. +cp "$GIT_CONFIG_GLOBAL" "$tmp/good" +printf '[x\n' >> "$GIT_CONFIG_GLOBAL" +out="$("$ctl" list)"; rc=$? +check "list broken file: rc" 1 "$rc" +check "list broken file: error= line" 1 "$([[ "$out" == error=* ]] && echo 1)" +cp "$tmp/good" "$GIT_CONFIG_GLOBAL" + "$ctl" bogus >/dev/null 2>&1; rc=$? check "usage: rc" 2 "$rc" |
