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

IJHack / QtPass / 35468717610

19 Sep 2026 08:52PM UTC coverage: 78.059% (-0.1%) from 78.167%
35468717610

Pull #1851

github

web-flow
Merge e377412f2 into 17bd44c92
Pull Request #1851: Store walks visit real directories only; junctions and links are not entries

84 of 129 new or added lines in 6 files covered. (65.12%)

9 existing lines in 3 files now uncovered.

6884 of 8819 relevant lines covered (78.06%)

74.73 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