Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package QtPass for openSUSE:Factory checked 
in at 2026-10-07 19:32:04
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/QtPass (Old)
 and      /work/SRC/openSUSE:Factory/.QtPass.new.3698736 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "QtPass"

Wed Oct  7 19:32:04 2026 rev:11 rq:1382843 version:1.8.3

Changes:
--------
--- /work/SRC/openSUSE:Factory/QtPass/QtPass.changes    2026-09-28 
10:41:00.655274405 +0200
+++ /work/SRC/openSUSE:Factory/.QtPass.new.3698736/QtPass.changes       
2026-10-07 19:32:42.100882218 +0200
@@ -1,0 +2,12 @@
+Tue Oct 06 15:58:08 UTC 2026 - ecsos <[email protected]>
+
+- Update to version 1.8.3:
+  * Release 1.8.3: dated 2026-10-06 (#1971)
+  * Release 1.8.3 (#1969)
+  * 1.8: a save that never reports must not trap the dialog (ported from 
#1965) (#1966)
+  * 1.8 ci: drop the Qt 6.11.2 pin, 6.11.3's checksums are served (#1964)
+  * metainfo: real sizes for the qtpass.org screenshots (#1954)
+  * 1.8: keep the entry when saving it fails (ported from #1951) (#1952)
+  * 1.8 Flatpak: build v1.8.2, private store, as on Flathub (#1935)
+
+-------------------------------------------------------------------

Old:
----
  QtPass-1.8.2.tar.xz

New:
----
  QtPass-1.8.3.tar.xz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ QtPass.spec ++++++
--- /var/tmp/diff_new_pack.Mv9VMS/_old  2026-10-07 19:32:42.840913123 +0200
+++ /var/tmp/diff_new_pack.Mv9VMS/_new  2026-10-07 19:32:42.841913165 +0200
@@ -18,7 +18,7 @@
 
 %define         _name qtpass
 Name:           QtPass
-Version:        1.8.2
+Version:        1.8.3
 Release:        0
 Summary:        A multi-platform gui for pass
 License:        GPL-3.0-only

++++++ QtPass-1.8.2.tar.xz -> QtPass-1.8.3.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/QtPass-1.8.2/CHANGELOG.md 
new/QtPass-1.8.3/CHANGELOG.md
--- old/QtPass-1.8.2/CHANGELOG.md       2026-09-24 23:00:10.000000000 +0200
+++ new/QtPass-1.8.3/CHANGELOG.md       2026-10-06 13:09:31.000000000 +0200
@@ -1,5 +1,19 @@
 # Changelog
 
+## [1.8.3](https://github.com/IJHack/QtPass/tree/v1.8.3) (2026-10-06)
+
+### Bugfixes
+
+- Saving an entry no longer loses what was typed when it fails. The password
+  dialog closed as soon as OK was pressed and gpg ran afterwards, so a missing
+  or expired recipient key or an unusable `.gpg-id` lost the entry. The dialog
+  now stays open until the entry is written, locked and showing "Saving…"; on
+  failure the fields come back with the reason. Cancel, Esc and closing the 
window
+  still work while it waits, after asking, so a save that never reports (an
+  unanswered PIN prompt) cannot trap you
+  ([#1944](https://github.com/IJHack/QtPass/issues/1944), ported from
+  [#1951](https://github.com/IJHack/QtPass/pull/1951))
+
 ## [1.8.2](https://github.com/IJHack/QtPass/tree/v1.8.2) (2026-09-24)
 
 ### Changed
@@ -10,7 +24,7 @@
   file themselves need the new name; the Flatpak manifest drops
   `rename-appdata-file` with this tag
 
-### Bugfixes
+### Bugfixes <!-- markdownlint-disable-line MD024 -->
 
 - "Automatically push" and "Automatically pull" in the settings did nothing
   since 1.8.0: [#1140](https://github.com/IJHack/QtPass/pull/1140) started
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/QtPass-1.8.2/Doxyfile new/QtPass-1.8.3/Doxyfile
--- old/QtPass-1.8.2/Doxyfile   2026-09-24 23:00:10.000000000 +0200
+++ new/QtPass-1.8.3/Doxyfile   2026-10-06 13:09:31.000000000 +0200
@@ -48,7 +48,7 @@
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 1.8.2
+PROJECT_NUMBER         = 1.8.3
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewers a
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/QtPass-1.8.2/flatpak/README.md 
new/QtPass-1.8.3/flatpak/README.md
--- old/QtPass-1.8.2/flatpak/README.md  2026-09-24 23:00:10.000000000 +0200
+++ new/QtPass-1.8.3/flatpak/README.md  2026-10-06 13:09:31.000000000 +0200
@@ -29,8 +29,9 @@
 ## How GnuPG works inside the sandbox
 
 The sandbox has no access to `~/.gnupg`. The runtime-provided `gpg` (only the
-`gpg2` wrapper is ours) therefore keeps its own keyring in 
`~/.var/app/org.qtpass.QtPass/.gnupg`, but it connects to the
-**host** `gpg-agent` through the read-only exposed socket directory
+`gpg2` wrapper is ours) therefore keeps its own keyring in
+`~/.var/app/org.qtpass.QtPass/.gnupg`, but it connects to the **host**
+`gpg-agent` through the read-only exposed socket directory
 (`--filesystem=xdg-run/gnupg:ro`). Private keys, pinentry dialogs and
 smartcards stay on the host; only the public keys have to be known inside the
 sandbox. Import them once:
@@ -43,31 +44,47 @@
 or paste them via **Users → Import key → From clipboard** in QtPass. The
 host agent must be running (it is, on any desktop where `gpg` already works).
 
+## The password store
+
+The default store is private to the Flatpak:
+`~/.var/app/org.qtpass.QtPass/.password-store` (`--persist=.password-store`).
+To use an existing store, for example `~/.password-store` shared with `pass`
+on the host, pick it with the folder button in Settings (Profiles page); the
+portal gives access to that folder only. Alternatively, grant the folder to
+QtPass alone with Flatseal or:
+
+```sh
+flatpak override --user --filesystem=~/.password-store org.qtpass.QtPass
+```
+
 ## Permissions, and what is deliberately left out
 
-Granted: Wayland/X11, network (for `git` over https), `~/.password-store`,
-the gpg-agent socket directory (read-only), a persistent private `~/.gnupg`,
-and the tray (`org.kde.StatusNotifierWatcher`). Nothing here needs a
-Flathub linter exception.
-
-Not granted on purpose: `~/.gnupg` itself, `~/.ssh` and the ssh-agent socket.
-A store in another location can be chosen through the file dialog (the portal
-grants access to the chosen folder); for `git` over SSH add
-`--socket=ssh-auth` (and `--filesystem=~/.ssh:ro` for `known_hosts`) with
-Flatseal or `flatpak override --user`.
+Granted: Wayland/X11, network (for `git` over https), the gpg-agent socket
+directory (read-only), a persistent private `~/.password-store` and
+`~/.gnupg`, and the tray (`org.kde.StatusNotifierWatcher`). Nothing here needs
+a Flathub linter exception.
+
+Not granted on purpose: any host directory, `~/.gnupg` itself, `~/.ssh` and
+the ssh-agent socket. For `git` over SSH, grant them with Flatseal or:
+
+```sh
+flatpak override --user --socket=ssh-auth --filesystem=~/.ssh:ro 
org.qtpass.QtPass
+```
+
+(`~/.ssh` read-only is for `known_hosts`.) Without the application ID,
+`flatpak override` changes the defaults of every installed Flatpak.
 
 ## Updating
 
 Bump the `tag`/`commit` of the `qtpass` module to the new release. The
 `x-checker-data` blocks let Flathub's external-data-checker propose updates of
-the bundled tools automatically. The `file` overlays in the `qtpass` module
-(metainfo, square icon, `main.cpp`, `qtpasssettings.cpp`, and for the 1.8.1
-first-run fix `mainwindow.h`, `mainwindow.cpp`, `qtpass.cpp`) exist only 
because
-v1.8.0 predates those changes — drop them when building from a later tag.
-Since 1.8.2 the metainfo is installed under its component ID
-(`org.qtpass.QtPass.metainfo.xml`): drop `rename-appdata-file` when the
-module moves to the 1.8.2 tag (`ci-manifest.sh` strips it for the tree build
-meanwhile).
+the bundled tools automatically. The module builds the tag as-is: since
+v1.8.1 the tree ships `flatpak/gpg2`, and `make -C main install` puts the
+desktop file, metainfo and icons where `rename-desktop-file` /
+`rename-appdata-file` / `rename-icon` expect them, so no working-tree files
+are overlaid any more. Since 1.8.2 the metainfo is installed under its
+component ID (`org.qtpass.QtPass.metainfo.xml`), so the manifest no longer
+needs `rename-appdata-file`.
 
 ## CI
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/QtPass-1.8.2/flatpak/org.qtpass.QtPass.yml 
new/QtPass-1.8.3/flatpak/org.qtpass.QtPass.yml
--- old/QtPass-1.8.2/flatpak/org.qtpass.QtPass.yml      2026-09-24 
23:00:10.000000000 +0200
+++ new/QtPass-1.8.3/flatpak/org.qtpass.QtPass.yml      2026-10-06 
13:09:31.000000000 +0200
@@ -1,20 +1,5 @@
 # SPDX-FileCopyrightText: 2026 Anne Jan Brouwer
 # SPDX-License-Identifier: GPL-3.0-or-later
-#
-# Flatpak manifest for QtPass. Build and install locally with:
-#
-#   flatpak run org.flatpak.Builder --user --install --force-clean \
-#     --install-deps-from=flathub build-dir flatpak/org.qtpass.QtPass.yml
-#
-# GnuPG comes from the KDE runtime. The sandboxed gpg keeps its own keyring
-# (persisted under ~/.var/app/org.qtpass.QtPass/.gnupg) but talks to the HOST
-# gpg-agent through the read-only exposed socket directory, so private keys,
-# pinentry and smartcards stay on the host. Import your public key once:
-#
-#   gpg --export --armor [email protected] | \
-#     flatpak run --command=gpg2 org.qtpass.QtPass --import
-#
-# or use Users -> Import key -> From clipboard inside QtPass.
 id: org.qtpass.QtPass
 runtime: org.kde.Platform
 runtime-version: "6.11"
@@ -22,23 +7,15 @@
 command: qtpass
 rename-desktop-file: qtpass.desktop
 rename-icon: qtpass-icon
-# v1.8.1 installs the metainfo as qtpass.appdata.xml; from 1.8.2 on it is
-# installed under the component id. Drop this line with the 1.8.2 tag.
-rename-appdata-file: qtpass.appdata.xml
 finish-args:
   - --share=ipc
   - --socket=fallback-x11
   - --socket=wayland
   - --device=dri
-  # git pull/push to remotes (https; add ssh-auth via Flatseal if you need ssh)
   - --share=network
-  # the pass default store location; pick another one through the file dialog
-  - --filesystem=~/.password-store:create
-  # host gpg-agent socket (connect works through a read-only bind mount)
   - --filesystem=xdg-run/gnupg:ro
-  # persistent keyring for the sandboxed gpg, at its default homedir
+  - --persist=.password-store
   - --persist=.gnupg
-  # tray icon
   - --talk-name=org.kde.StatusNotifierWatcher
 cleanup:
   - /include
@@ -104,7 +81,6 @@
   - name: qrencode
     buildsystem: cmake-ninja
     config-opts:
-      # 4.1.1 still says cmake_minimum_required(2.8); CMake 4 refuses that
       - -DCMAKE_POLICY_VERSION_MINIMUM=3.5
       - -DWITH_TOOLS=YES
       - -DWITH_TESTS=NO
@@ -124,7 +100,6 @@
     build-commands:
       - make PREFIX=/app WITH_ALLCOMP=no install
     sources:
-      # cgit snapshot tarballs are not checksum-stable; pin the tag's commit
       - type: git
         url: https://git.zx2c4.com/password-store
         tag: "1.7.4"
@@ -133,22 +108,15 @@
   - name: qtpass
     buildsystem: simple
     build-commands:
-      # the KDE SDK ships Qt 6 qmake as plain "qmake"
       - qmake PREFIX=/app CONFIG+=release qtpass.pro
       - make -j$FLATPAK_BUILDER_N_JOBS sub-src sub-main
-      # installs bin/qtpass, the desktop file, the metainfo and the hicolor
-      # icons under the upstream names; rename-desktop-file / rename-icon
-      # (and rename-appdata-file, for v1.8.1) rewrite them to the app id
       - make -C main install
-      # QtPassSettings::initExecutables() (and pass) look for "gpg2"; the 
runtime
-      # ships "gpg". The wrapper also scrubs sandbox-only env before the host
-      # gpg-agent sees it — see the comments in flatpak/gpg2.
       - install -Dm755 flatpak/gpg2 /app/bin/gpg2
     sources:
       - type: git
         url: https://github.com/IJHack/QtPass.git
-        tag: v1.8.1
-        commit: 1a389718013586bf21e3b6d603d437f57a055c88
+        tag: v1.8.2
+        commit: 71122182ddaef33d83c60868a7baff4a85442f1a
         x-checker-data:
           type: git
           tag-pattern: ^v([\d.]+)$
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/QtPass-1.8.2/org.qtpass.QtPass.metainfo.xml 
new/QtPass-1.8.3/org.qtpass.QtPass.metainfo.xml
--- old/QtPass-1.8.2/org.qtpass.QtPass.metainfo.xml     2026-09-24 
23:00:10.000000000 +0200
+++ new/QtPass-1.8.3/org.qtpass.QtPass.metainfo.xml     2026-10-06 
13:09:31.000000000 +0200
@@ -62,15 +62,21 @@
     <screenshot type="default">
       <caption>QtPass main window</caption>
       <caption xml:lang="nl_NL">QtPass hoofdscherm</caption>
-      <image width="768" 
height="596">https://qtpass.org/images/qtpass.png</image>
+      <image width="1111" 
height="961">https://qtpass.org/images/qtpass.png</image>
     </screenshot>
     <screenshot>
       <caption>QtPass Configuration window</caption>
       <caption xml:lang="nl_NL">QtPass Configuratie scherm</caption>
-      <image width="900" 
height="637">https://qtpass.org/images/config.png</image>
+      <image width="822" 
height="921">https://qtpass.org/images/config.png</image>
     </screenshot>
   </screenshots>
   <releases>
+    <release version="1.8.3" date="2026-10-06">
+      <url>https://github.com/IJHack/QtPass/releases/tag/v1.8.3</url>
+      <description>
+        <p>Bug-fix release: a failed save no longer loses what was typed. The 
password dialog stays open until the entry is written and shows why when 
encryption fails, and a save that never finishes can still be cancelled.</p>
+      </description>
+    </release>
     <release version="1.8.2" date="2026-09-24">
       <url>https://github.com/IJHack/QtPass/releases/tag/v1.8.2</url>
       <description>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/QtPass-1.8.2/qtpass.iss new/QtPass-1.8.3/qtpass.iss
--- old/QtPass-1.8.2/qtpass.iss 2026-09-24 23:00:10.000000000 +0200
+++ new/QtPass-1.8.3/qtpass.iss 2026-10-06 13:09:31.000000000 +0200
@@ -1,6 +1,6 @@
 #define MyAppName "QtPass"
 #ifndef MyAppVersion
-  #define MyAppVersion "1.8.2"
+  #define MyAppVersion "1.8.3"
 #endif
 #define MyAppPublisher "IJHack"
 #define MyAppURL "https://qtpass.org/";
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/QtPass-1.8.2/qtpass.pri new/QtPass-1.8.3/qtpass.pri
--- old/QtPass-1.8.2/qtpass.pri 2026-09-24 23:00:10.000000000 +0200
+++ new/QtPass-1.8.3/qtpass.pri 2026-10-06 13:09:31.000000000 +0200
@@ -1,7 +1,7 @@
 # QtPass - GUI for pass
 # SPDX-FileCopyrightText: 2014 Anne Jan Brouwer
 
-VERSION    = 1.8.2
+VERSION    = 1.8.3
 
 CONFIG(coverage) {
        QMAKE_LFLAGS += --coverage
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/QtPass-1.8.2/qtpass.spec new/QtPass-1.8.3/qtpass.spec
--- old/QtPass-1.8.2/qtpass.spec        2026-09-24 23:00:10.000000000 +0200
+++ new/QtPass-1.8.3/qtpass.spec        2026-10-06 13:09:31.000000000 +0200
@@ -7,7 +7,7 @@
 # This should probably be part of a release process.
 
 Name: qtpass
-Version: 1.8.2
+Version: 1.8.3
 Release: 1%{?dist}
 Summary: QtPass is a multi-platform GUI for pass, the standard unix password 
manager.
 License: GPLv3
@@ -61,6 +61,9 @@
 /usr/bin/gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || :
 
 %changelog
+* Tue Oct 06 2026 Anne Jan Brouwer <[email protected]> 1.8.3
+- Updated QtPass
+
 * Thu Sep 24 2026 Anne Jan Brouwer <[email protected]> 1.8.2
 - Updated QtPass
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/QtPass-1.8.2/src/imitatepass.cpp 
new/QtPass-1.8.3/src/imitatepass.cpp
--- old/QtPass-1.8.2/src/imitatepass.cpp        2026-09-24 23:00:10.000000000 
+0200
+++ new/QtPass-1.8.3/src/imitatepass.cpp        2026-10-06 13:09:31.000000000 
+0200
@@ -146,6 +146,7 @@
  * @param overwrite whether to overwrite existing file
  */
 void ImitatePass::Insert(QString file, QString newValue, bool overwrite) {
+  m_insertWritten = false;
   file = file + ".gpg";
   QString gpgIdPath = Pass::getGpgIdPath(file, m_settings.passStore);
   if (!verifyGpgIdFile(gpgIdPath)) {
@@ -1158,6 +1159,10 @@
 #ifdef QT_DEBUG
   dbg() << "Imitate Pass";
 #endif
+  if (id == PASS_INSERT && exitCode == 0) {
+    // gpg writes the entry itself (--output); git add and commit follow.
+    m_insertWritten = true;
+  }
   PROCESS pid = transactionIsOver(static_cast<PROCESS>(id));
   m_transactionOutput.append(out);
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/QtPass-1.8.2/src/imitatepass.h 
new/QtPass-1.8.3/src/imitatepass.h
--- old/QtPass-1.8.2/src/imitatepass.h  2026-09-24 23:00:10.000000000 +0200
+++ new/QtPass-1.8.3/src/imitatepass.h  2026-10-06 13:09:31.000000000 +0200
@@ -201,6 +201,17 @@
                 const QString &err) override;
 
   /**
+   * @brief Whether the failed insert's gpg step had written the entry
+   * before a later git step failed.
+   * @return true when the entry is in the store.
+   */
+  [[nodiscard]] auto insertWritten() const -> bool override {
+    return m_insertWritten;
+  }
+  /// The last Insert()'s gpg wrote its entry; its git steps may still fail.
+  bool m_insertWritten = false;
+
+  /**
    * @brief Open a transaction for every execution.
    *
    * In native (imitate) mode each wrapped command is a git/gpg transaction;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/QtPass-1.8.2/src/pass.cpp 
new/QtPass-1.8.3/src/pass.cpp
--- old/QtPass-1.8.2/src/pass.cpp       2026-09-24 23:00:10.000000000 +0200
+++ new/QtPass-1.8.3/src/pass.cpp       2026-10-06 13:09:31.000000000 +0200
@@ -726,10 +726,11 @@
 
   if (pid == PASS_INSERT) {
     const QString friendly = gpgErrorMessage(err);
-    if (!friendly.isEmpty()) {
-      emit processErrorExit(exitCode, formatInsertError(friendly, err));
-      return;
-    }
+    const QString why =
+        friendly.isEmpty() ? err : formatInsertError(friendly, err);
+    emit insertFailed(why, insertWritten());
+    emit processErrorExit(exitCode, why);
+    return;
   }
 
   emit processErrorExit(exitCode, err);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/QtPass-1.8.2/src/pass.h new/QtPass-1.8.3/src/pass.h
--- old/QtPass-1.8.2/src/pass.h 2026-09-24 23:00:10.000000000 +0200
+++ new/QtPass-1.8.3/src/pass.h 2026-10-06 13:09:31.000000000 +0200
@@ -275,6 +275,14 @@
 
 protected:
   /**
+   * @brief Whether the insert that just failed had put its entry in the
+   * store before a later step failed. The backend that writes the entry
+   * knows; this default says no.
+   * @return true when the failed insert's entry was written.
+   */
+  [[nodiscard]] virtual auto insertWritten() const -> bool { return false; }
+
+  /**
    * @brief Execute external wrapper command.
    * @param id Process identifier.
    * @param app Executable path.
@@ -382,6 +390,17 @@
   void processErrorExit(int exitCode, const QString &err);
 
   /**
+   * @brief Emitted when an Insert() fails after it started a process,
+   *        alongside the generic processErrorExit(); lets the password
+   *        dialog keep what was typed and say why, without reacting to any
+   *        other command failing meanwhile.
+   * @param err What gpg or pass reported.
+   * @param written true when this insert did put the entry in the store and
+   *        only a later step (git add or commit) failed.
+   */
+  void insertFailed(const QString &err, bool written);
+
+  /**
    * @brief Emitted when any operation finishes.
    * @param out Standard output from the process.
    * @param err Standard error from the process.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/QtPass-1.8.2/src/passworddialog.cpp 
new/QtPass-1.8.3/src/passworddialog.cpp
--- old/QtPass-1.8.2/src/passworddialog.cpp     2026-09-24 23:00:10.000000000 
+0200
+++ new/QtPass-1.8.3/src/passworddialog.cpp     2026-10-06 13:09:31.000000000 
+0200
@@ -17,6 +17,7 @@
 #include <QIcon>
 #include <QLabel>
 #include <QLineEdit>
+#include <QMessageBox>
 #include <QPushButton>
 #include <QShortcut>
 #include <utility>
@@ -71,7 +72,9 @@
 
   connect(m_pass, &Pass::finishedShow, this, &PasswordDialog::setPass);
   connect(m_pass, &Pass::processErrorExit, this, &PasswordDialog::onShowError);
-  connect(this, &PasswordDialog::accepted, this, &PasswordDialog::on_accepted);
+  connect(m_pass, &Pass::finishedInsert, this,
+          &PasswordDialog::onInsertFinished);
+  connect(m_pass, &Pass::insertFailed, this, &PasswordDialog::onInsertFailed);
   connect(this, &PasswordDialog::rejected, this, &PasswordDialog::on_rejected);
 
   if (!isNew) {
@@ -127,10 +130,34 @@
   ui->widget->setEnabled(true);
 }
 
+void PasswordDialog::accept() {
+  if (m_saving) {
+    return;
+  }
+  save();
+}
+
+void PasswordDialog::reject() {
+  if (m_saving) {
+    const QMessageBox::StandardButton answer = QMessageBox::question(
+        this, tr("Still saving"),
+        tr("The entry is still being saved. Close the dialog anyway? If "
+           "saving then fails, what you typed is lost."),
+        QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
+    // The insert may have reported while the question was up.
+    if (answer != QMessageBox::Yes || !m_saving) {
+      return;
+    }
+    // Whatever the insert reports later, the main window shows it.
+    m_saving = false;
+  }
+  QDialog::reject();
+}
+
 /**
- * @brief PasswordDialog::on_accepted handle Ok click for QDialog
+ * @brief PasswordDialog::save insert the entry and wait for the result.
  */
-void PasswordDialog::on_accepted() {
+void PasswordDialog::save() {
   // For an existing entry, refuse to save until its decrypted content has
   // loaded (Show is asynchronous). getPassword() always returns at least a
   // newline, so the previous isEmpty() check never fired and clicking OK early
@@ -147,7 +174,60 @@
     newValue += "\n";
   }
 
+  setSaving(true);
+  // A refusal (an unusable or unsigned .gpg-id) comes through critical()
+  // before Insert() returns, with nothing started that could still report.
+  QString refusal;
+  const QMetaObject::Connection refused =
+      connect(m_pass, &Pass::critical, this,
+              [&refusal](const QString &, const QString &message) {
+                refusal = message;
+              });
   m_pass->Insert(m_file, newValue, !m_isNew);
+  disconnect(refused);
+  if (m_saving && !refusal.isEmpty()) {
+    setSaving(false);
+    showNotSaved(refusal);
+  }
+}
+
+void PasswordDialog::setSaving(bool saving) {
+  m_saving = saving;
+  setEditorEnabled(!saving);
+  // Cancel stays: a save that never reports (an unanswered PIN prompt, a
+  // queue held up by a hanging pull) must not leave quitting as the only
+  // way out. reject() asks first.
+  ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!saving);
+  ui->statusLabel->setText(saving ? tr("Saving…") : QString());
+}
+
+void PasswordDialog::onInsertFinished() {
+  // Only one insert can be in flight while this modal dialog waits: an
+  // earlier dialog closed only once its own had reported.
+  if (!m_saving) {
+    return;
+  }
+  setSaving(false);
+  QDialog::accept();
+}
+
+void PasswordDialog::onInsertFailed(const QString &err, bool written) {
+  if (!m_saving) {
+    return;
+  }
+  setSaving(false);
+  // The entry is in the store and a later step (git add or commit) failed,
+  // which the main window reports. Kept open, a new entry could only be
+  // tried again under a name it now has.
+  if (m_isNew && written) {
+    QDialog::accept();
+    return;
+  }
+  showNotSaved(err);
+}
+
+void PasswordDialog::showNotSaved(const QString &err) {
+  ui->statusLabel->setText(tr("Not saved: %1").arg(err.trimmed()));
 }
 
 /**
@@ -457,6 +537,10 @@
  * @param templateName Name of template to apply.
  */
 void PasswordDialog::applyTemplate(const QString &templateName) {
+  // Another template would rebuild the field rows under a pending insert.
+  if (m_saving) {
+    return;
+  }
   auto it = m_availableTemplates.constFind(templateName);
   if (it != m_availableTemplates.constEnd()) {
     m_currentTemplateName = templateName;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/QtPass-1.8.2/src/passworddialog.h 
new/QtPass-1.8.3/src/passworddialog.h
--- old/QtPass-1.8.2/src/passworddialog.h       2026-09-24 23:00:10.000000000 
+0200
+++ new/QtPass-1.8.3/src/passworddialog.h       2026-10-06 13:09:31.000000000 
+0200
@@ -48,6 +48,25 @@
   ~PasswordDialog() override;
 
   /**
+   * @brief OK: insert the entry and close as accepted only once the insert
+   * has succeeded.
+   *
+   * The insert is asynchronous. Until it reports back the dialog stays open
+   * with its fields locked; when it fails (a missing recipient key, an
+   * unusable `.gpg-id`) the fields come back with what was typed and the
+   * reason is shown, instead of the entry being lost with the closed dialog.
+   */
+  void accept() override;
+
+  /**
+   * @brief Cancel, Esc and the window's close button. While an insert is in
+   * flight they ask first: closing then means a failure can no longer hand
+   * back what was typed, but a save that never reports must not leave
+   * quitting as the only way out.
+   */
+  void reject() override;
+
+  /**
    * @brief Populate the dialog's password field with the given text.
    * @param password Password text to display.
    * @sa getPassword
@@ -119,10 +138,22 @@
 private slots:
   void on_checkBoxShow_stateChanged(int arg1);
   void on_createPasswordButton_clicked();
-  void on_accepted();
   void on_rejected();
 
   /**
+   * @brief The insert started by accept() succeeded: close as accepted.
+   */
+  void onInsertFinished();
+  /**
+   * @brief The insert started by accept() failed: unlock the fields, which
+   * still hold what was typed, and show why.
+   * @param err The reason reported by the backend.
+   * @param written true when the entry was written and only a later step
+   *        (git) failed; a new entry then closes the dialog.
+   */
+  void onInsertFailed(const QString &err, bool written);
+
+  /**
    * @brief Handle a process error while waiting for an entry's decrypt.
    *
    * When the asynchronous Show() fails, keep the dialog open and surface the
@@ -149,6 +180,21 @@
    */
   void setEditorEnabled(bool enabled);
   /**
+   * @brief Start inserting what the dialog holds and wait for the result.
+   */
+  void save();
+  /**
+   * @brief Lock or unlock the dialog while an insert is in flight.
+   * @param saving true while waiting for the backend.
+   */
+  void setSaving(bool saving);
+  /**
+   * @brief Say why the entry was not saved and let the user try again; the
+   * fields keep what was typed.
+   * @param err The reason reported by the backend.
+   */
+  void showNotSaved(const QString &err);
+  /**
    * @brief Connect validation and normalisation to the OTP field, if present.
    *
    * Called whenever the field widgets are rebuilt, since both setTemplate()
@@ -176,9 +222,11 @@
   bool m_allFields{};
   bool m_isNew{};
   /// True once the existing entry's decrypted content has been loaded, so
-  /// on_accepted() can refuse to overwrite it with empty fields before the
+  /// save() can refuse to overwrite it with empty fields before the
   /// asynchronous Show completes.
   bool m_contentLoaded{};
+  /// True from accept() starting an insert until the backend reports back.
+  bool m_saving{};
   /// Last pwgen mode passed to usePwgen(), so the policy can be re-applied
   /// after setEditorEnabled() re-enables the character-set widgets.
   bool m_usePwgen{};
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/QtPass-1.8.2/src/realpass.cpp 
new/QtPass-1.8.3/src/realpass.cpp
--- old/QtPass-1.8.2/src/realpass.cpp   2026-09-24 23:00:10.000000000 +0200
+++ new/QtPass-1.8.3/src/realpass.cpp   2026-10-06 13:09:31.000000000 +0200
@@ -77,6 +77,11 @@
  * @brief RealPass::Insert pass insert
  */
 void RealPass::Insert(QString file, QString newValue, bool overwrite) {
+  m_insertTarget =
+      QDir::cleanPath(QDir::isAbsolutePath(file)
+                          ? file + ".gpg"
+                          : QDir(m_settings.passStore).filePath(file) + 
".gpg");
+  m_insertTargetExisted = QFileInfo::exists(m_insertTarget);
   QStringList args = {"insert", "-m"};
   if (overwrite) {
     args.append("-f");
@@ -85,6 +90,11 @@
   executePass(PASS_INSERT, args, newValue);
 }
 
+auto RealPass::insertWritten() const -> bool {
+  return !m_insertTarget.isEmpty() && !m_insertTargetExisted &&
+         QFileInfo::exists(m_insertTarget);
+}
+
 /**
  * @brief RealPass::Remove pass remove wrapper
  */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/QtPass-1.8.2/src/realpass.h 
new/QtPass-1.8.3/src/realpass.h
--- old/QtPass-1.8.2/src/realpass.h     2026-09-24 23:00:10.000000000 +0200
+++ new/QtPass-1.8.3/src/realpass.h     2026-10-06 13:09:31.000000000 +0200
@@ -42,6 +42,18 @@
   void passMoveOrCopy(PROCESS id, const QString &subcommand, const QString 
&src,
                       const QString &dest, bool force);
 
+  /**
+   * @brief Whether the failed `pass insert` wrote its entry before its git
+   * step failed. pass does not say, so: the entry did not exist when the
+   * insert started and does now.
+   * @return true when a new entry appeared.
+   */
+  [[nodiscard]] auto insertWritten() const -> bool override;
+
+  /// The entry the last Insert() writes, and whether it existed beforehand.
+  QString m_insertTarget;
+  bool m_insertTargetExisted = false;
+
 public:
   /**
    * @brief Construct a RealPass instance.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/QtPass-1.8.2/tests/auto/passworddialog/tst_passworddialog.cpp 
new/QtPass-1.8.3/tests/auto/passworddialog/tst_passworddialog.cpp
--- old/QtPass-1.8.2/tests/auto/passworddialog/tst_passworddialog.cpp   
2026-09-24 23:00:10.000000000 +0200
+++ new/QtPass-1.8.3/tests/auto/passworddialog/tst_passworddialog.cpp   
2026-10-06 13:09:31.000000000 +0200
@@ -15,9 +15,11 @@
 #include <QDialogButtonBox>
 #include <QLabel>
 #include <QLineEdit>
+#include <QMessageBox>
 #include <QPlainTextEdit>
 #include <QPushButton>
 #include <QSignalSpy>
+#include <QTimer>
 #include <QToolButton>
 #include <QtTest>
 
@@ -45,7 +47,34 @@
   void GitPush() override {}
   void Show(QString) override {}
   void OtpGenerate(QString) override {}
-  void Insert(QString, QString, bool) override {}
+  void Insert(QString file, QString, bool) override {
+    inserted = file;
+    // The backends refuse before starting anything through critical(),
+    // and report a started insert asynchronously.
+    if (!refuseWith.isEmpty()) {
+      emit critical(QStringLiteral("Can not edit"), refuseWith);
+      return;
+    }
+    if (holdResult) {
+      return;
+    }
+    if (!failWith.isEmpty()) {
+      QTimer::singleShot(0, this, [this, why = failWith] {
+        emit insertFailed(why, failWritten);
+      });
+      return;
+    }
+    emit finishedInsert(QString(), QString());
+  }
+  QString inserted;
+  /// Refuse the next Insert() synchronously with this reason.
+  QString refuseWith;
+  /// Fail the next Insert() asynchronously with this reason.
+  QString failWith;
+  /// Whether that failure says the entry was written (a git step failed).
+  bool failWritten = false;
+  /// Report nothing yet, as while gpg is still running.
+  bool holdResult = false;
   void Remove(QString, bool) override {}
   void Move(const QString, const QString, const bool) override {}
   void Copy(const QString, const QString, const bool) override {}
@@ -72,6 +101,11 @@
   void initTestCase() { isolateTestSettings(); }
 
   void existingEntryLocksEditorUntilContentLoads();
+  void failedInsertKeepsWhatWasTyped();
+  void refusedInsertKeepsWhatWasTyped();
+  void savingCancelAsksBeforeClosing();
+  void savingIgnoresATemplateSwitch();
+  void newEntryWrittenBeforeAFailedGitStepCloses();
   void contentLoadUnlocksEditorAndOk();
   void decryptErrorKeepsDialogOpenWithReason();
   void lateErrorAfterContentLoadIsIgnored();
@@ -169,5 +203,147 @@
   QVERIFY2(pw->isEnabled(), "a new entry must be editable immediately");
 }
 
+/**
+ * @brief #1944: when encrypting fails (a missing recipient key, say) the
+ *        dialog stays open with what was typed and says why; OK then tries
+ *        again. It used to close before gpg ran, and the entry was lost.
+ */
+void tst_passworddialog::failedInsertKeepsWhatWasTyped() {
+  FakePass pass;
+  pass.failWith = QStringLiteral("gpg: [email protected]: No public key");
+  PasswordDialog d(&pass, QtPassSettings::load(), QStringLiteral("mail"), 
true);
+  auto *password = d.findChild<QLineEdit 
*>(QStringLiteral("lineEditPassword"));
+  auto *status = d.findChild<QLabel *>(QStringLiteral("statusLabel"));
+  QVERIFY(password != nullptr && status != nullptr);
+  password->setText(QStringLiteral("s3cret"));
+  QSignalSpy accepted(&d, &QDialog::accepted);
+
+  okButton(d)->click();
+  QTRY_VERIFY2(status->text().contains(QStringLiteral("No public key")),
+               qPrintable("the reason must be shown: " + status->text()));
+  QVERIFY2(accepted.isEmpty(), "a failed save must not close the dialog");
+  QCOMPARE(password->text(), QStringLiteral("s3cret"));
+  QVERIFY(password->isEnabled() && okButton(d)->isEnabled());
+
+  pass.failWith.clear();
+  okButton(d)->click();
+  QCOMPARE(accepted.size(), 1);
+  QCOMPARE(pass.inserted, QStringLiteral("mail"));
+}
+
+/**
+ * @brief A refusal before anything starts (an unusable .gpg-id) keeps the
+ *        dialog open the same way.
+ */
+void tst_passworddialog::refusedInsertKeepsWhatWasTyped() {
+  FakePass pass;
+  pass.refuseWith = QStringLiteral("Could not read encryption key to use");
+  PasswordDialog d(&pass, QtPassSettings::load(), QStringLiteral("mail"), 
true);
+  auto *password = d.findChild<QLineEdit 
*>(QStringLiteral("lineEditPassword"));
+  password->setText(QStringLiteral("s3cret"));
+  QSignalSpy accepted(&d, &QDialog::accepted);
+
+  okButton(d)->click();
+  QVERIFY2(accepted.isEmpty(), "a refused save must not close the dialog");
+  QVERIFY(d.findChild<QLabel *>(QStringLiteral("statusLabel"))
+              ->text()
+              .contains(QStringLiteral("encryption key")));
+  QCOMPARE(password->text(), QStringLiteral("s3cret"));
+  QVERIFY(okButton(d)->isEnabled());
+}
+
+namespace {
+/// Answer the next modal question box with @p button once it is up.
+void answerNextQuestion(QMessageBox::StandardButton button, int tries = 50) {
+  QTimer::singleShot(10, [button, tries] {
+    auto *box = qobject_cast<QMessageBox *>(QApplication::activeModalWidget());
+    if (box == nullptr) {
+      if (tries > 0) {
+        answerNextQuestion(button, tries - 1);
+      }
+      return;
+    }
+    box->button(button)->click();
+  });
+}
+} // namespace
+
+/**
+ * @brief A save that never reports (an unanswered PIN prompt, a queue held
+ *        up by a hanging pull) must not trap the user: Cancel stays on while
+ *        saving, and Cancel, Esc or closing ask first. No keeps waiting; Yes
+ *        closes, and a result arriving later no longer touches the dialog.
+ */
+void tst_passworddialog::savingCancelAsksBeforeClosing() {
+  FakePass pass;
+  pass.holdResult = true;
+  PasswordDialog d(&pass, QtPassSettings::load(), QStringLiteral("mail"), 
true);
+  d.findChild<QLineEdit *>(QStringLiteral("lineEditPassword"))
+      ->setText(QStringLiteral("s3cret"));
+  d.show();
+  QVERIFY(QTest::qWaitForWindowExposed(&d));
+  QSignalSpy rejected(&d, &QDialog::rejected);
+  QSignalSpy accepted(&d, &QDialog::accepted);
+  auto *box = d.findChild<QDialogButtonBox *>(QStringLiteral("buttonBox"));
+
+  okButton(d)->click();
+  QVERIFY2(box->button(QDialogButtonBox::Cancel)->isEnabled(),
+           "Cancel is the way out of a save that never reports");
+  answerNextQuestion(QMessageBox::No);
+  d.reject();
+  QVERIFY2(rejected.isEmpty() && d.isVisible(), "No keeps waiting");
+  answerNextQuestion(QMessageBox::No);
+  QTest::keyClick(&d, Qt::Key_Escape);
+  QVERIFY2(rejected.isEmpty() && d.isVisible(), "Esc asks the same");
+
+  answerNextQuestion(QMessageBox::Yes);
+  box->button(QDialogButtonBox::Cancel)->click();
+  QCOMPARE(rejected.size(), 1);
+  emit pass.finishedInsert(QString(), QString());
+  emit pass.insertFailed(QStringLiteral("late"), false);
+  QVERIFY2(accepted.isEmpty(), "a late result does not reopen or accept");
+}
+
+/**
+ * @brief Ctrl+T while an insert is pending would rebuild the field rows
+ *        under it; the switch is ignored until the result.
+ */
+void tst_passworddialog::savingIgnoresATemplateSwitch() {
+  FakePass pass;
+  pass.holdResult = true;
+  PasswordDialog d(&pass, QtPassSettings::load(), QStringLiteral("mail"), 
true);
+  QHash<QString, QStringList> templates;
+  templates.insert(QStringLiteral("login"), {QStringLiteral("login")});
+  templates.insert(QStringLiteral("card"), {QStringLiteral("number")});
+  d.setAvailableTemplates(templates, QStringLiteral("login"));
+  auto *login = d.findChild<QLineEdit *>(QStringLiteral("login"));
+  QVERIFY(login != nullptr);
+  login->setText(QStringLiteral("bob"));
+
+  okButton(d)->click();
+  d.cycleTemplate();
+  QVERIFY2(d.findChild<QLineEdit *>(QStringLiteral("login")) != nullptr,
+           "the typed field must survive a template switch while saving");
+  emit pass.insertFailed(QStringLiteral("gpg: no public key"), false);
+  QCOMPARE(d.findChild<QLineEdit *>(QStringLiteral("login"))->text(),
+           QStringLiteral("bob"));
+}
+
+/**
+ * @brief When the entry was written and only a later step failed (git add
+ *        or commit), the dialog closes: the entry is saved, and the main
+ *        window reports the git error.
+ */
+void tst_passworddialog::newEntryWrittenBeforeAFailedGitStepCloses() {
+  FakePass pass;
+  pass.failWith = QStringLiteral("fatal: unable to write new index file");
+  pass.failWritten = true;
+  PasswordDialog d(&pass, QtPassSettings::load(), QStringLiteral("mail"), 
true);
+  QSignalSpy accepted(&d, &QDialog::accepted);
+
+  okButton(d)->click();
+  QTRY_COMPARE(accepted.size(), 1);
+}
+
 QTEST_MAIN(tst_passworddialog)
 #include "tst_passworddialog.moc"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/QtPass-1.8.2/tests/auto/util/tst_util.cpp 
new/QtPass-1.8.3/tests/auto/util/tst_util.cpp
--- old/QtPass-1.8.2/tests/auto/util/tst_util.cpp       2026-09-24 
23:00:10.000000000 +0200
+++ new/QtPass-1.8.3/tests/auto/util/tst_util.cpp       2026-10-06 
13:09:31.000000000 +0200
@@ -101,6 +101,7 @@
       Pass::finished(id, exitCode, out, err);
     }
     bool callCreateBackupCommit() { return createBackupCommit(); }
+    bool written() const { return insertWritten(); }
   };
 
   template <typename T, void (*Setter)(const T &)> struct SettingGuard {
@@ -249,6 +250,8 @@
   void parseGrepOutputPlainTextHeaders();
   // Pass::finished PASS_GREP exit-code handling
   void passFinishedGrepNoMatchEmitsEmpty();
+  void passFinishedInsertErrorEmitsInsertFailed();
+  void imitatePassInsertIsWrittenOnceGpgSucceeded();
   void passFinishedGrepErrorEmitsProcessError();
   void passFinishedGrepSuccessEmitsResults();
   // ImitatePass::Grep / helpers
@@ -345,6 +348,42 @@
 }
 
 /**
+ * @brief Any failed insert reaches the password dialog through insertFailed
+ *        (#1944), with the message processErrorExit carries; other commands
+ *        failing do not.
+ */
+void tst_util::passFinishedInsertErrorEmitsInsertFailed() {
+  TestPass pass;
+  QSignalSpy failedSpy(&pass, &Pass::insertFailed);
+  QSignalSpy errSpy(&pass, &Pass::processErrorExit);
+  pass.callPassFinished(static_cast<int>(Enums::PASS_INSERT), 1, QString(),
+                        QStringLiteral("fatal: unable to write index\n"));
+  QCOMPARE(failedSpy.count(), 1);
+  QCOMPARE(errSpy.count(), 1);
+  QCOMPARE(failedSpy.at(0).at(0).toString(), errSpy.at(0).at(1).toString());
+  QVERIFY2(!failedSpy.at(0).at(1).toBool(), "no insert wrote anything");
+
+  pass.callPassFinished(static_cast<int>(Enums::GIT_PUSH), 1, QString(),
+                        QStringLiteral("rejected\n"));
+  QCOMPARE(failedSpy.count(), 1);
+}
+
+/**
+ * @brief ImitatePass's gpg writes the entry itself: once that step succeeded
+ *        a failing git step no longer means nothing was saved.
+ */
+void tst_util::imitatePassInsertIsWrittenOnceGpgSucceeded() {
+  TestPass pass;
+  QVERIFY(!pass.written());
+  pass.callFinished(static_cast<int>(Enums::PASS_INSERT), 2, QString(),
+                    QStringLiteral("gpg: encryption failed\n"));
+  QVERIFY2(!pass.written(), "a failed gpg wrote nothing");
+  pass.callFinished(static_cast<int>(Enums::PASS_INSERT), 0, QString(),
+                    QString());
+  QVERIFY(pass.written());
+}
+
+/**
  * @brief tst_util::normalizeFolderPath test to check correct working
  * of Util::normalizeFolderPath the paths should always end with '/'
  */

++++++ _service ++++++
--- /var/tmp/diff_new_pack.Mv9VMS/_old  2026-10-07 19:32:43.248930163 +0200
+++ /var/tmp/diff_new_pack.Mv9VMS/_new  2026-10-07 19:32:43.252930330 +0200
@@ -6,7 +6,7 @@
       <param name="exclude">.git</param>
       <param name="versionformat">@PARENT_TAG@</param>
       <param name="versionrewrite-pattern">v(.*)</param>
-      <param name="revision">v1.8.2</param>
+      <param name="revision">v1.8.3</param>
       <param name="changesgenerate">enable</param>
       </service>
    <service name="recompress" mode="manual">

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.Mv9VMS/_old  2026-10-07 19:32:43.295932126 +0200
+++ /var/tmp/diff_new_pack.Mv9VMS/_new  2026-10-07 19:32:43.299932293 +0200
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param name="url">https://github.com/IJHack/QtPass.git</param>
-              <param 
name="changesrevision">71122182ddaef33d83c60868a7baff4a85442f1a</param></service></servicedata>
+              <param 
name="changesrevision">dadb11b7638603421acedea6f0e0cf034e0a93df</param></service></servicedata>
 (No newline at EOF)
 

Reply via email to