• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IJHack / QtPass / 35467906150

19 Sep 2026 08:36PM UTC coverage: 77.868% (-0.3%) from 78.167%
35467906150

Pull #1851

github

web-flow
Merge 27c744ca6 into d67d8e6cc
Pull Request #1851: Store walks visit real directories only; junctions and links are not entries

74 of 117 new or added lines in 6 files covered. (63.25%)

97 existing lines in 6 files now uncovered.

6843 of 8788 relevant lines covered (77.87%)

74.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.91
/src/profileinit.cpp
1
// SPDX-FileCopyrightText: 2026 Anne Jan Brouwer
2
// SPDX-License-Identifier: GPL-3.0-or-later
3
#include "profileinit.h"
4
#include "appsettings.h"
5
#include "executor.h"
6
#include "gpgidsigner.h"
7
#include "userinfo.h"
8
#include "util.h"
9
#include <QDir>
10
#include <QFile>
11
#include <QFileInfo>
12
#include <QProcess>
13

14
auto ProfileInit::needsInit(const QString &path) -> bool {
7✔
15
  if (path.isEmpty()) {
7✔
16
    return false;
17
  }
18
  QDir dir(path);
6✔
19
  if (!dir.exists()) {
6✔
20
    return false;
21
  }
22
  return !dir.exists(".gpg-id");
5✔
23
}
6✔
24

25
auto ProfileInit::initialise(const QString &dir, const QList<UserInfo> &users,
7✔
26
                             const AppSettings &s, bool useGit, QString *note)
27
    -> bool {
28
  QString scratch;
7✔
29
  QString &out = note != nullptr ? *note : scratch;
7✔
30
  out.clear();
7✔
31

32
  const QDir folder(dir);
7✔
33
  if (!folder.exists() && !QDir().mkpath(folder.absolutePath())) {
10✔
34
    out = tr("Could not create %1.").arg(folder.absolutePath());
×
35
    return false;
×
36
  }
37
  const QString gpgIdFile = folder.filePath(QStringLiteral(".gpg-id"));
14✔
38
  if (!writeGpgId(gpgIdFile, users, &out)) {
7✔
39
    return false;
40
  }
41
  QString sigFile;
6✔
42
  if (!s.passSigningKey.trimmed().isEmpty()) {
12✔
43
    if (!signGpgId(gpgIdFile, s, &out)) {
×
44
      return false;
45
    }
46
    sigFile = gpgIdFile + QStringLiteral(".sig");
×
47
  }
48
  if (useGit &&
7✔
49
      !commitGpgId(folder.absolutePath(), gpgIdFile, sigFile, s, &out)) {
7✔
50
    return false;
51
  }
52

53
  const QStringList existing = folder.entryList(
6✔
54
      {QStringLiteral("*.gpg")}, QDir::Files | QDir::NoDotAndDotDot);
18✔
55
  if (!existing.isEmpty()) {
6✔
56
    out = tr("%1 already contains %n encrypted file(s); they were not "
1✔
57
             "re-encrypted. Switch to the profile and open Users to do that.",
58
             nullptr, static_cast<int>(existing.size()))
59
              .arg(folder.absolutePath());
2✔
60
  }
61
  return true;
62
}
7✔
63

64
auto ProfileInit::writeGpgId(const QString &gpgIdFile,
7✔
65
                             const QList<UserInfo> &users, QString *note)
66
    -> bool {
67
  QStringList ids;
7✔
68
  for (const UserInfo &user : users) {
19✔
69
    if (user.enabled) {
12✔
70
      ids << user.key_id;
6✔
71
    }
72
  }
73
  if (ids.isEmpty()) {
7✔
74
    *note = tr("No recipient selected; %1 was not written.").arg(gpgIdFile);
2✔
75
    return false;
1✔
76
  }
77
  QFile file(gpgIdFile);
6✔
78
  if (!file.open(QIODevice::WriteOnly | QIODevice::Text) ||
12✔
79
      file.write((ids.join(QLatin1Char('\n')) + QLatin1Char('\n')).toUtf8()) <
36✔
80
          0) {
81
    *note = tr("Could not write %1: %2").arg(gpgIdFile, file.errorString());
×
82
    return false;
×
83
  }
84
  file.close();
6✔
85
  // Same as ImitatePass::writeGpgIdFile: the recipient list leaks which keys
86
  // the store is encrypted to, so keep it owner-only where that means
87
  // anything.
88
  QFile::setPermissions(gpgIdFile, QFile::ReadOwner | QFile::WriteOwner);
6✔
89
  return true;
90
}
6✔
91

92
auto ProfileInit::signGpgId(const QString &gpgIdFile, const AppSettings &s,
×
93
                            QString *note) -> bool {
94
  const GpgIdSigner signer(s.gpgExecutable,
95
                           GpgIdSigner::keysFromSetting(s.passSigningKey));
×
96
  QString err;
×
97
  if (!signer.sign(gpgIdFile, &err)) {
×
98
    *note = tr("Could not sign %1 with %2: %3")
×
99
                .arg(gpgIdFile, signer.keys().first(), err.trimmed());
×
100
    return false;
×
101
  }
102
  return true;
103
}
×
104

105
auto ProfileInit::commitGpgId(const QString &dir, const QString &gpgIdFile,
1✔
106
                              const QString &sigFile, const AppSettings &s,
107
                              QString *note) -> bool {
108
  // A process of our own in the profile directory: nothing here may touch
109
  // the shared Executor or PASSWORD_STORE_DIR of the active store.
110
  QProcess git;
1✔
111
  git.setWorkingDirectory(dir);
1✔
112
  auto run = [&](const QStringList &args) -> bool {
3✔
113
    QString err;
3✔
114
    const int rc = Executor::executeBlocking(git, s.gitExecutable, args,
3✔
115
                                             QString(), nullptr, &err);
3✔
116
    if (rc != 0) {
3✔
117
      *note =
118
          tr("git %1 failed in %2: %3").arg(args.first(), dir, err.trimmed());
×
119
      return false;
×
120
    }
121
    return true;
122
  };
1✔
123
  QStringList files{QFileInfo(gpgIdFile).fileName()};
3✔
124
  if (!sigFile.isEmpty()) {
1✔
125
    files << QFileInfo(sigFile).fileName();
×
126
  }
127
  const QStringList add =
128
      QStringList{QStringLiteral("add"), QStringLiteral("--")} + files;
4✔
129
  const QStringList commit =
130
      QStringList{QStringLiteral("commit"), QStringLiteral("-m"),
7✔
131
                  QStringLiteral("Added .gpg-id using QtPass."),
1✔
132
                  QStringLiteral("--")} +
5✔
133
      files;
134
  return run({QStringLiteral("init")}) && run(add) && run(commit);
4✔
135
}
2✔
136

137
auto ProfileInit::initGit(const QString &dir, const AppSettings &s,
×
138
                          QString *note) -> bool {
139
  QProcess git;
×
140
  git.setWorkingDirectory(dir);
×
141
  auto run = [&](const QStringList &args) -> bool {
×
142
    QString err;
×
143
    const int rc = Executor::executeBlocking(git, s.gitExecutable, args,
×
144
                                             QString(), nullptr, &err);
×
145
    if (rc != 0) {
×
146
      *note =
147
          tr("git %1 failed in %2: %3").arg(args.first(), dir, err.trimmed());
×
148
      return false;
×
149
    }
150
    return true;
151
  };
×
152
  // Only what belongs to a store is staged: an existing folder may hold
153
  // exports, editor swap files or other plaintext that must not enter the
154
  // history (the same rule the re-encryption backup commit follows).
155
  // Regular files only: a link or junction is not part of the store and
156
  // must not have what it points to staged.
UNCOV
157
  QStringList files;
×
UNCOV
158
  const QDir base(dir);
×
159
  // Hidden directories (.git among them) are not walked, hidden files are:
160
  // .gpg-id is one.
NEW
161
  const QStringList found = Util::regularFilesUnder(
×
162
      dir,
NEW
163
      {QStringLiteral("*.gpg"), QStringLiteral(".gpg-id"),
×
NEW
164
       QStringLiteral(".gpg-id.sig")},
×
NEW
165
      nullptr, true);
×
NEW
166
  for (const QString &path : found) {
×
NEW
167
    files << base.relativeFilePath(path);
×
168
  }
169
  if (!run({QStringLiteral("init")})) {
×
170
    return false;
171
  }
172
  if (!files.isEmpty() &&
×
173
      !run(QStringList{QStringLiteral("add"), QStringLiteral("--")} + files)) {
×
174
    return false;
175
  }
176
  return run({QStringLiteral("commit"), QStringLiteral("--allow-empty"),
×
177
              QStringLiteral("-m"),
×
178
              QStringLiteral("Added password store using QtPass.")});
×
179
}
×
180

181
auto ProfileInit::gitIdentityConfigured(const QString &dir,
4✔
182
                                        const AppSettings &s) -> bool {
183
  QProcess git;
4✔
184
  git.setWorkingDirectory(dir);
4✔
185
  for (const char *key : {"user.name", "user.email"}) {
8✔
186
    QString out;
6✔
187
    if (Executor::executeBlocking(git, s.gitExecutable,
30✔
188
                                  {QStringLiteral("config"),
6✔
189
                                   QStringLiteral("--get"),
6✔
190
                                   QString::fromLatin1(key)},
191
                                  QString(), &out, nullptr) != 0 ||
16✔
192
        out.trimmed().isEmpty()) {
6✔
193
      return false;
194
    }
195
  }
196
  return true;
2✔
197
}
16✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc