aboutsummaryrefslogtreecommitdiffstats
path: root/desktop
diff options
context:
space:
mode:
Diffstat (limited to 'desktop')
-rw-r--r--desktop/modules/git/GitPage.qml27
-rw-r--r--desktop/modules/git/README.md8
-rwxr-xr-xdesktop/modules/git/gitcfg26
-rwxr-xr-xdesktop/modules/git/test-gitcfg.sh19
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"