1#include "GitRepoLoader.h"
2
3#include <GitBase.h>
4#include <GitBranches.h>
5#include <GitCache.h>
6#include <GitConfig.h>
7#include <GitLocal.h>
8#include <GitQlientSettings.h>
9#include <GitRequestorProcess.h>
10#include <GitWip.h>
11
12#include <QLogger.h>
13
14#include <QDir>
15
16using namespace QLogger;
17
18static const char *GIT_LOG_FORMAT("%m%HX%P%n%cn<%ce>%n%an<%ae>%n%at%n%s%n%b ");
19
20GitRepoLoader::GitRepoLoader(QSharedPointer<GitBase> gitBase, QSharedPointer<GitCache> cache,
21 const QSharedPointer<GitQlientSettings> &settings, QObject *parent)
22 : QObject(parent)
23 , mGitBase(gitBase)
24 , mRevCache(std::move(cache))
25 , mSettings(settings)
26{
27}
28
29void GitRepoLoader::cancelAll()
30{
31 emit cancelAllProcesses(QPrivateSignal());
32}
33
34void GitRepoLoader::loadLogHistory()
35{
36 if (mLocked)
37 QLog_Warning("Git", "Git is currently loading data.");
38 else
39 {
40 if (mGitBase->getWorkingDir().isEmpty())
41 QLog_Error("Git", "No working directory set.");
42 else
43 {
44 mRefreshReferences = true;
45 mLocked = true;
46
47 if (configureRepoDirectory())
48 {
49 mGitBase->updateCurrentBranch();
50
51 QLog_Info("Git", "Requesting references...");
52
53 mSteps = 1;
54
55 requestRevisions();
56 }
57 else
58 QLog_Error("Git", "The working directory is not a Git repository.");
59 }
60 }
61}
62
63void GitRepoLoader::loadReferences()
64{
65 if (mLocked)
66 QLog_Warning("Git", "Git is currently loading data.");
67 else
68 {
69 if (mGitBase->getWorkingDir().isEmpty())
70 QLog_Error("Git", "No working directory set.");
71 else
72 {
73 mRefreshReferences = true;
74 mLocked = true;
75
76 if (configureRepoDirectory())
77 {
78 mGitBase->updateCurrentBranch();
79
80 QLog_Info("Git", "Requesting references...");
81
82 mSteps = 1;
83
84 requestReferences();
85 }
86 else
87 QLog_Error("Git", "The working directory is not a Git repository.");
88 }
89 }
90}
91
92void GitRepoLoader::loadAll()
93{
94 if (mLocked)
95 QLog_Warning("Git", "Git is currently loading data.");
96 else
97 {
98 if (mGitBase->getWorkingDir().isEmpty())
99 QLog_Error("Git", "No working directory set.");
100 else
101 {
102 mRefreshReferences = true;
103 mLocked = true;
104
105 if (configureRepoDirectory())
106 {
107 mGitBase->updateCurrentBranch();
108
109 QLog_Info("Git", "Requesting revisions and referencecs...");
110
111 mSteps = 2;
112
113 requestRevisions();
114 requestReferences();
115 }
116 else
117 QLog_Error("Git", "The working directory is not a Git repository.");
118 }
119 }
120}
121
122bool GitRepoLoader::configureRepoDirectory()
123{
124 QLog_Debug("Git", "Configuring repository directory.");
125
126 const auto ret = mGitBase->run("git rev-parse --show-cdup");
127
128 if (ret.success)
129 {
130 QDir d(QString("%1/%2").arg(mGitBase->getWorkingDir(), ret.output.trimmed()));
131 mGitBase->setWorkingDir(d.absolutePath());
132
133 return true;
134 }
135
136 return false;
137}
138
139void GitRepoLoader::requestReferences()
140{
141 QLog_Debug("Git", "Loading references...");
142
143 const auto requestor = new GitRequestorProcess(mGitBase->getWorkingDir());
144 connect(requestor, &GitRequestorProcess::procDataReady, this, &GitRepoLoader::processReferences);
145 connect(this, &GitRepoLoader::cancelAllProcesses, requestor, &AGitProcess::onCancel);
146
147 requestor->run("git show-ref -d");
148}
149
150void GitRepoLoader::processReferences(QByteArray ba)
151{
152 if (mRefreshReferences)
153 mRevCache->clearReferences();
154
155 QString prevRefSha;
156 const auto referencesList = ba.split('\n');
157
158 for (const auto &reference : referencesList)
159 {
160 if (!reference.isEmpty())
161 {
162 auto revSha = QString::fromUtf8(reference.left(40));
163 const auto refName = reference.mid(41);
164
165 if (!refName.startsWith("refs/tags/") || (refName.startsWith("refs/tags/") && refName.endsWith("^{}")))
166 {
167 References::Type type;
168 QString name;
169
170 if (refName.startsWith("refs/tags/"))
171 {
172 type = References::Type::LocalTag;
173 name = QString::fromUtf8(refName.mid(10, reference.length()));
174 name.remove("^{}");
175 }
176 else if (refName.startsWith("refs/heads/"))
177 {
178 type = References::Type::LocalBranch;
179 name = QString::fromUtf8(refName.mid(11));
180 }
181 else if (refName.startsWith("refs/remotes/") && !refName.endsWith("HEAD"))
182 {
183 type = References::Type::RemoteBranches;
184 name = QString::fromUtf8(refName.mid(13));
185 }
186 else
187 continue;
188
189 mRevCache->insertReference(revSha, type, name);
190 }
191 prevRefSha = revSha;
192 }
193 }
194
195 mRevCache->reloadCurrentBranchInfo(mGitBase->getCurrentBranch(), mGitBase->getLastCommit().output.trimmed());
196
197 --mSteps;
198
199 if (mSteps == 0)
200 {
201 mRevCache->setConfigurationDone();
202
203 emit signalLoadingFinished(mRefreshReferences);
204
205 mLocked = false;
206 mRefreshReferences = false;
207 }
208}
209
210void GitRepoLoader::requestRevisions()
211{
212 QLog_Debug("Git", "Loading revisions...");
213
214 const auto maxCommits = mSettings->localValue("MaxCommits", 0).toInt();
215 const auto commitsToRetrieve = maxCommits != 0 ? QString::fromUtf8("-n %1").arg(maxCommits)
216 : mShowAll ? QString("--all") : mGitBase->getCurrentBranch();
217
218 QString order;
219
220 switch (mSettings->localValue("GraphSortingOrder", 0).toInt())
221 {
222 case 0:
223 order = "--author-date-order";
224 break;
225 case 1:
226 order = "--date-order";
227 break;
228 case 2:
229 order = "--topo-order";
230 break;
231 default:
232 order = "--author-date-order";
233 break;
234 }
235
236 const auto baseCmd = QString("git log %1 --no-color --log-size --parents --boundary -z --pretty=format:%2 %3")
237 .arg(order, QString::fromUtf8(GIT_LOG_FORMAT), commitsToRetrieve);
238
239 if (!mRevCache->isInitialized())
240 emit signalLoadingStarted();
241
242 const auto requestor = new GitRequestorProcess(mGitBase->getWorkingDir());
243 connect(requestor, &GitRequestorProcess::procDataReady, this, &GitRepoLoader::processRevisions);
244 connect(this, &GitRepoLoader::cancelAllProcesses, requestor, &AGitProcess::onCancel);
245
246 requestor->run(baseCmd);
247}
248
249void GitRepoLoader::processRevisions(QByteArray ba)
250{
251 QLog_Info("Git", "Revisions received!");
252
253 QScopedPointer<GitConfig> gitConfig(new GitConfig(mGitBase));
254 const auto serverUrl = gitConfig->getServerHost();
255
256 if (serverUrl.contains("github"))
257 QLog_Info("Git", "Requesting PR status!");
258
259 QLog_Debug("Git", "Processing revisions...");
260
261 const auto initialized = mRevCache->isInitialized();
262
263 if (!initialized)
264 emit signalLoadingStarted();
265
266 const auto ret = gitConfig->getGitValue("log.showSignature");
267 const auto showSignature = ret.success ? ret.output.contains("true") : false;
268 auto commits = showSignature ? processSignedLog(ba) : processUnsignedLog(ba);
269 QScopedPointer<GitWip> git(new GitWip(mGitBase, mRevCache));
270 const auto files = git->getUntrackedFiles();
271
272 mRevCache->setUntrackedFilesList(std::move(files));
273 mRevCache->setup(git->getWipInfo(), std::move(commits));
274
275 --mSteps;
276
277 if (mSteps == 0)
278 {
279 mRevCache->setConfigurationDone();
280
281 emit signalLoadingFinished(mRefreshReferences);
282
283 mLocked = false;
284 mRefreshReferences = false;
285 }
286}
287
288QVector<CommitInfo> GitRepoLoader::processUnsignedLog(QByteArray &log) const
289{
290 auto lines = log.split('\000');
291 QVector<CommitInfo> commits;
292 commits.reserve(lines.count());
293
294 auto pos = 0;
295 while (!lines.isEmpty())
296 {
297 if (auto commit = CommitInfo { lines.takeFirst() }; commit.isValid())
298 {
299 commit.pos = ++pos;
300 commits.append(std::move(commit));
301 }
302 }
303
304 return commits;
305}
306
307QVector<CommitInfo> GitRepoLoader::processSignedLog(QByteArray &log) const
308{
309 log.replace('\000', '\n');
310
311 QVector<CommitInfo> commits;
312
313 QByteArray commit;
314 QByteArray gpg;
315 QString gpgKey;
316 auto processingCommit = false;
317 auto pos = 1;
318 auto start = 0;
319 int end;
320 bool goodSignature = false;
321
322 while ((end = log.indexOf('\n', start)) != -1)
323 {
324 QByteArray line(log.mid(start, end - start));
325 start = end + 1;
326
327 if (line.startsWith("gpg: "))
328 {
329 processingCommit = false;
330 gpg.append(line);
331
332 if (line.contains("using RSA key"))
333 {
334#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
335 gpgKey = QString::fromUtf8(line).split("using RSA key", Qt::SkipEmptyParts).last();
336#else
337 gpgKey = QString::fromUtf8(line).split("using RSA key", QString::SkipEmptyParts).last();
338#endif
339 gpgKey.append('\n');
340 }
341 }
342 else if (line.startsWith("log size"))
343 {
344 if (!commit.isEmpty())
345 {
346 if (auto revision = CommitInfo { commit, gpgKey, goodSignature }; revision.isValid())
347 {
348 revision.pos = pos++;
349 commits.append(std::move(revision));
350
351 gpgKey.clear();
352 }
353
354 commit.clear();
355 }
356 processingCommit = true;
357
358 if (!gpg.isEmpty())
359 {
360 goodSignature = gpg.contains("Good signature");
361 gpg.clear();
362 }
363 else
364 goodSignature = false;
365 }
366 else if (processingCommit)
367 commit.append(line + '\n');
368 }
369
370 return commits;
371}
372