1#include "gitmanager.h"
2#include "../utils.h"
3#include "../settings.h"
4
5#include <QDir>
6#include <QFileInfo>
7
8GitManager::GitManager(QObject *parent) : QObject(parent)
9{
10}
11
12void GitManager::createRepository(const QString &folder)
13{
14 QString currentBranch;
15 if (hasRepository(folder,currentBranch))
16 throw GitError(tr("Folder \"%1\" already has a repository!"));
17 QStringList args;
18 args.append("init");
19 runGit(folder,args);
20
21 QStringList contents;
22 contents.append(".git");
23 contents.append("*.o");
24 contents.append("*.exe");
25 contents.append("*.layout");
26#ifdef Q_OS_LINUX
27 contents.append("*.");
28#endif
29
30 QDir dir(folder);
31 stringsToFile(contents,dir.filePath(".gitignore"));
32 QString output;
33 add(folder,".gitignore",output);
34}
35
36bool GitManager::hasRepository(const QString &folder, QString& currentBranch)
37{
38 if (folder.isEmpty())
39 return false;
40 QStringList args;
41 args.append("status");
42 args.append("-b");
43 args.append("-u");
44 args.append("no");
45 args.append("--ignored=no");
46 QString output = runGit(folder,args);
47 bool result = output.startsWith("On branch");
48 if (result) {
49 int pos = QString("On branch").length();
50 while (pos<output.length() && output[pos].isSpace())
51 pos++;
52 int endPos = pos;
53 while (endPos<output.length() && !output[endPos].isSpace())
54 endPos++;
55 currentBranch = output.mid(pos,endPos-pos);
56 }
57 return result;
58}
59
60QString GitManager::rootFolder(const QString &folder)
61{
62 QStringList args;
63 args.append("rev-parse");
64 args.append("--show-toplevel");
65 return runGit(folder,args).trimmed();
66}
67
68bool GitManager::isFileInRepository(const QFileInfo& fileInfo)
69{
70 QStringList args;
71 args.append("ls-files");
72 args.append(fileInfo.fileName());
73 QString output = runGit(fileInfo.absolutePath(),args);
74 return output.trimmed() == fileInfo.fileName();
75}
76
77bool GitManager::isFileStaged(const QFileInfo &fileInfo)
78{
79 QStringList args;
80 args.append("diff");
81 args.append("--staged");
82 args.append("--name-only");
83 args.append(fileInfo.fileName());
84 QString output = runGit(fileInfo.absolutePath(),args);
85 return output.trimmed() == fileInfo.fileName();
86}
87
88bool GitManager::isFileChanged(const QFileInfo &fileInfo)
89{
90 QStringList args;
91 args.append("diff");
92 args.append("--name-only");
93 args.append(fileInfo.fileName());
94 QString output = runGit(fileInfo.absolutePath(),args);
95 return output.trimmed() == fileInfo.fileName();
96}
97
98bool GitManager::add(const QString &folder, const QString &path, QString& output)
99{
100 QStringList args;
101 args.append("add");
102 args.append(path);
103 output = runGit(folder,args);
104 return isSuccess(output);
105}
106
107bool GitManager::remove(const QString &folder, const QString &path, QString& output)
108{
109 QStringList args;
110 args.append("rm");
111 args.append(path);
112 output = runGit(folder,args);
113 return isSuccess(output);
114}
115
116bool GitManager::rename(const QString &folder, const QString &oldName,
117 const QString &newName, QString& output)
118{
119 QStringList args;
120 args.append("mv");
121 args.append(oldName);
122 args.append(newName);
123 output = runGit(folder,args);
124 return isSuccess(output);
125}
126
127bool GitManager::restore(const QString &folder, const QString &path, QString& output)
128{
129 QStringList args;
130 args.append("restore");
131 if (path.isEmpty())
132 args.append(".");
133 else
134 args.append(path);
135 output = runGit(folder,args);
136 return isSuccess(output);
137}
138
139int GitManager::logCounts(const QString &folder, const QString &branch)
140{
141 QStringList args;
142 args.append("rev-list");
143 args.append("--count");
144 if (branch.isEmpty())
145 args.append("HEAD");
146 else
147 args.append(branch);
148 QString s = runGit(folder,args).trimmed();
149 bool ok;
150 int result = s.toInt(&ok);
151 if (!ok)
152 result = 0;
153 return result;
154}
155
156QList<PGitCommitInfo> GitManager::log(const QString &folder, int start, int count, const QString &branch)
157{
158 QStringList args;
159 args.append("log");
160 args.append("--skip");
161 args.append(QString("%1").arg(start));
162 args.append("-n");
163 args.append(QString("%1").arg(count));
164 args.append("--format=medium");
165 args.append("--date=iso-strict");
166 if (branch.isEmpty())
167 args.append("HEAD");
168 else
169 args.append(branch);
170 QString output = runGit(folder,args);
171 QStringList lines = textToLines(output);
172 QList<PGitCommitInfo> result;
173 int pos = 0;
174 PGitCommitInfo commitInfo;
175 while (pos<lines.length()) {
176 if (lines[pos].startsWith("commit ")) {
177 commitInfo = std::make_shared<GitCommitInfo>();
178 commitInfo->commitHash=lines[pos].mid(QString("commit ").length()).trimmed();
179 result.append(commitInfo);
180 } else if(!commitInfo) {
181 break;
182 } else if (lines[pos].startsWith("Author:")) {
183 commitInfo->author=lines[pos].mid(QString("Author:").length()).trimmed();
184 } else if (lines[pos].startsWith("Date:")) {
185 commitInfo->authorDate=QDateTime::fromString(lines[pos].mid(QString("Date:").length()).trimmed(),Qt::ISODate);
186 } else if (!lines[pos].trimmed().isEmpty()) {
187 if (commitInfo->title.isEmpty()) {
188 commitInfo->title = lines[pos].trimmed();
189 } else {
190 commitInfo->fullCommitMessage.append(lines[pos].trimmed()+"\n");
191 }
192 }
193 pos++;
194 }
195 return result;
196}
197
198QStringList GitManager::listFiles(const QString &folder)
199{
200 QStringList args;
201 args.append("ls-files");
202 return textToLines(runGit(folder,args));
203}
204
205QStringList GitManager::listStagedFiles(const QString &folder)
206{
207 QStringList args;
208 args.append("diff");
209 args.append("--staged");
210 args.append("--name-only");
211 return textToLines(runGit(folder,args));
212}
213
214QStringList GitManager::listChangedFiles(const QString &folder)
215{
216 QStringList args;
217 args.append("diff");
218 args.append("--name-only");
219 return textToLines(runGit(folder,args));
220}
221
222QStringList GitManager::listConflicts(const QString &folder)
223{
224 QStringList args;
225 args.append("diff");
226 args.append("--name-only");
227 args.append("--diff-filter=U");
228 return textToLines(runGit(folder,args));
229}
230
231QStringList GitManager::listRemotes(const QString &folder)
232{
233 QStringList args;
234 args.append("remote");
235 return textToLines(runGit(folder,args));
236}
237
238bool GitManager::removeRemote(const QString &folder, const QString &remoteName, QString& output)
239{
240 QStringList args;
241 args.append("remote");
242 args.append("remove");
243 args.append(remoteName);
244
245 output = runGit(folder,args);
246 return isSuccess(output);
247}
248
249bool GitManager::renameRemote(const QString &folder, const QString &oldName, const QString &newName, QString &output)
250{
251 QStringList args;
252 args.append("remote");
253 args.append("rename");
254 args.append(oldName);
255 args.append(newName);
256
257 output = runGit(folder,args);
258 return isSuccess(output);
259}
260
261bool GitManager::addRemote(const QString &folder, const QString &name, const QString &url, QString &output)
262{
263 QStringList args;
264 args.append("remote");
265 args.append("add");
266 args.append(name);
267 args.append(url);
268
269 output = runGit(folder,args);
270 return isSuccess(output);
271}
272
273bool GitManager::setRemoteURL(const QString &folder, const QString &name, const QString &newURL, QString &output)
274{
275 QStringList args;
276 args.append("remote");
277 args.append("set-url");
278 args.append(name);
279 args.append(newURL);
280
281 output = runGit(folder,args);
282 return isSuccess(output);
283}
284
285QString GitManager::getRemoteURL(const QString &folder, const QString &name)
286{
287 QStringList args;
288 args.append("remote");
289 args.append("get-url");
290 args.append(name);
291 return runGit(folder,args).trimmed();
292}
293
294QString GitManager::getBranchRemote(const QString &folder, const QString &branch)
295{
296 QStringList args;
297 args.append("config");
298 args.append("--get");
299 args.append(QString("branch.%1.remote").arg(branch));
300 return runGit(folder,args).trimmed();
301}
302
303QString GitManager::getBranchMerge(const QString &folder, const QString &branch)
304{
305 QStringList args;
306 args.append("config");
307 args.append("--get");
308 args.append(QString("branch.%1.merge").arg(branch));
309 return runGit(folder,args).trimmed();
310}
311
312bool GitManager::setBranchUpstream(
313 const QString &folder,
314 const QString &branch,
315 const QString &remoteName,
316 QString& output)
317{
318 QStringList args;
319 args.append("branch");
320 args.append(QString("--set-upstream-to=%1/%2").arg(remoteName,branch));
321 args.append(branch);
322 output = runGit(folder,args).trimmed();
323 return isSuccess(output);
324}
325
326bool GitManager::fetch(const QString &folder, QString &output)
327{
328 QStringList args;
329 args.append("fetch");
330 output = runGit(folder,args).trimmed();
331 return isSuccess(output);
332}
333
334bool GitManager::pull(const QString &folder, QString &output)
335{
336 QStringList args;
337 args.append("pull");
338 output = runGit(folder,args).trimmed();
339 return isSuccess(output);
340}
341
342bool GitManager::push(const QString &folder, QString &output)
343{
344 QStringList args;
345 args.append("push");
346 output = runGit(folder,args).trimmed();
347 return isSuccess(output);
348}
349
350bool GitManager::push(const QString &folder, const QString &remoteName, const QString &branch, QString &output)
351{
352 QStringList args;
353 args.append("push");
354 args.append("--set-upstream");
355 args.append(remoteName);
356 args.append(branch);
357 output = runGit(folder,args).trimmed();
358 return isSuccess(output);
359}
360
361bool GitManager::removeConfig(const QString &folder, const QString &name, QString &output)
362{
363 QStringList args;
364 args.append("config");
365 args.append("--unset-all");
366 args.append(name);
367 output = runGit(folder,args);
368 return isSuccess(output);
369}
370
371bool GitManager::setConfig(const QString &folder, const QString &name, const QString &value, QString &output)
372{
373 removeConfig(folder,name,output);
374 QStringList args;
375 args.append("config");
376 args.append("--add");
377 args.append(name);
378 args.append(value);
379 output = runGit(folder,args);
380 return isSuccess(output);
381}
382
383bool GitManager::setUserName(const QString &folder, const QString &userName, QString &output)
384{
385 return setConfig(folder,"user.name",userName,output);
386}
387
388bool GitManager::setUserEmail(const QString &folder, const QString &userEmail, QString &output)
389{
390 return setConfig(folder,"user.email",userEmail,output);
391}
392
393QString GitManager::getConfig(const QString& folder, const QString &name)
394{
395 QStringList args;
396 args.append("config");
397 args.append("--get");
398 args.append(name);
399 return runGit(folder,args).trimmed();
400}
401
402QString GitManager::getUserName(const QString& folder)
403{
404 return getConfig(folder, "user.name");
405}
406
407QString GitManager::getUserEmail(const QString& folder)
408{
409 return getConfig(folder, "user.email");
410}
411
412QStringList GitManager::listBranches(const QString &folder, int &current)
413{
414 QStringList args;
415 args.append("branch");
416 args.append("-a");
417 args.append("-l");
418 QStringList temp = textToLines(runGit(folder,args));
419 current = -1;
420 for (int i=0;i<temp.length();i++) {
421 QString s = temp[i];
422 if (s.startsWith('*')) {
423 current = i;
424 temp[i] = s.mid(1).trimmed();
425 } else if (s.startsWith('+')) {
426 temp[i] = s.mid(1).trimmed();
427 } else {
428 temp[i] = s.trimmed();
429 }
430 }
431 return temp;
432}
433
434bool GitManager::switchToBranch(const QString &folder, const QString &branch,
435 bool create, bool force, bool merge, bool track,
436 bool noTrack, bool forceCreation, QString& output)
437{
438 QStringList args;
439 args.append("switch");
440 if (forceCreation)
441 args.append("-C");
442 else if (create)
443 args.append("-c");
444 if (merge)
445 args.append("-m");
446 if (force)
447 args.append("-f");
448 if (track)
449 args.append("--track");
450 else if (noTrack)
451 args.append("--no-track");
452 args.append(branch);
453 output = runGit(folder,args);
454 return isSuccess(output);
455}
456
457bool GitManager::merge(const QString &folder, const QString &commit, bool squash,
458 bool fastForwardOnly, bool noFastForward, bool noCommit,
459 QString& output,
460 const QString& commitMessage)
461{
462 QStringList args;
463 args.append("merge");
464 if (squash)
465 args.append("--squash");
466 if (fastForwardOnly)
467 args.append("--ff-only");
468 else if (noFastForward)
469 args.append("--no-ff");
470 if (noCommit)
471 args.append("--no-commit");
472 if (!commitMessage.isEmpty()
473 && commitMessage != QObject::tr("<Auto Generated by Git>")){
474 args.append("-m");
475 args.append(commitMessage);
476 }
477 args.append(commit);
478 output = runGit(folder,args);
479 return isSuccess(output);
480}
481
482bool GitManager::continueMerge(const QString &folder)
483{
484 QStringList args;
485 args.append("merge");
486 args.append("--continue");
487 QString output = runGit(folder,args);
488 return isSuccess(output);
489
490}
491
492void GitManager::abortMerge(const QString &folder)
493{
494 QStringList args;
495 args.append("merge");
496 args.append("--abort");
497 runGit(folder,args);
498}
499
500bool GitManager::isSuccess(const QString &output)
501{
502 QStringList lst = textToLines(output);
503 if (!lst.isEmpty()) {
504 foreach (const QString& s, lst) {
505 QString last= s.trimmed();
506 if (last.startsWith("error:") || last.startsWith("fatal:"))
507 return false;
508 }
509 return true;
510 }
511 return true;
512}
513
514bool GitManager::clone(const QString &folder, const QString &url, QString& output)
515{
516 QStringList args;
517 args.append("clone");
518 args.append(url);
519 output = runGit(folder,args);
520 return isSuccess(output);
521}
522
523bool GitManager::commit(const QString &folder, const QString &message, bool autoStage, QString& output)
524{
525 QStringList args;
526 args.append("commit");
527 if (autoStage)
528 args.append("-a");
529 args.append("-m");
530 args.append(message);
531 output = runGit(folder,args);
532 return isSuccess(output);
533}
534
535bool GitManager::revert(const QString &folder, QString& output)
536{
537 QStringList args;
538 args.append("revert");
539 output = runGit(folder,args);
540 return isSuccess(output);
541}
542
543bool GitManager::reset(const QString &folder, const QString &commit,
544 GitResetStrategy strategy,
545 QString& output)
546{
547 //todo reset type
548 QStringList args;
549 args.append("reset");
550 switch(strategy) {
551 case GitResetStrategy::Soft:
552 args.append("--soft");
553 break;
554 case GitResetStrategy::Hard:
555 args.append("--hard");
556 break;
557 case GitResetStrategy::Mixed:
558 args.append("--mixed");
559 break;
560 case GitResetStrategy::Merge:
561 args.append("--merge");
562 break;
563 case GitResetStrategy::Keep:
564 args.append("--keep");
565 break;
566 }
567 args.append(commit);
568 output = runGit(folder,args);
569 return isSuccess(output);
570}
571
572bool GitManager::isValid()
573{
574 return pSettings->vcs().gitOk();
575}
576
577QString GitManager::runGit(const QString& workingFolder, const QStringList &args)
578{
579 if (!isValid())
580 return "";
581 QFileInfo fileInfo(pSettings->vcs().gitPath());
582 if (!fileInfo.exists())
583 return "fatal: git doesn't exist";
584 emit gitCmdRunning(QString("Running in \"%1\": \n \"%2\" \"%3\"")
585 .arg(workingFolder,
586 pSettings->vcs().gitPath(),
587 args.join("\" \"")));
588// qDebug()<<"---------";
589// qDebug()<<args;
590 QProcessEnvironment env;
591#ifdef Q_OS_WIN
592 env.insert("PATH",pSettings->dirs().appDir());
593 env.insert("GIT_ASKPASS",includeTrailingPathDelimiter(pSettings->dirs().appDir())+"redpanda-win-git-askpass.exe");
594#elif defined(Q_OS_LINUX)
595 env.insert(QProcessEnvironment::systemEnvironment());
596 env.insert("LANG","en");
597 env.insert("LANGUAGE","en");
598 env.insert("GIT_ASKPASS",includeTrailingPathDelimiter(pSettings->dirs().appLibexecDir())+"redpanda-git-askpass");
599#endif
600 QString output = runAndGetOutput(
601 fileInfo.absoluteFilePath(),
602 workingFolder,
603 args,
604 "",
605 false,
606 env);
607 output = escapeUTF8String(output.toUtf8());
608// qDebug()<<output;
609 emit gitCmdFinished(output);
610// if (output.startsWith("fatal:"))
611// throw GitError(output);
612 return output;
613}
614
615QString GitManager::escapeUTF8String(const QByteArray &rawString)
616{
617 QByteArray stringValue;
618 int p = 0;
619 while (p<rawString.length()) {
620 char ch = rawString[p];
621 if (ch =='\\' && p+1 < rawString.length()) {
622 p++;
623 ch = rawString[p];
624 switch (ch) {
625 case '\'':
626 stringValue+=0x27;
627 p++;
628 break;
629 case '"':
630 stringValue+=0x22;
631 p++;
632 break;
633 case '?':
634 stringValue+=0x3f;
635 p++;
636 break;
637 case '\\':
638 stringValue+=0x5c;
639 p++;
640 break;
641 case 'a':
642 stringValue+=0x07;
643 p++;
644 break;
645 case 'b':
646 stringValue+=0x08;
647 p++;
648 break;
649 case 'f':
650 stringValue+=0x0c;
651 p++;
652 break;
653 case 'n':
654 stringValue+=0x0a;
655 p++;
656 break;
657 case 'r':
658 stringValue+=0x0d;
659 p++;
660 break;
661 case 't':
662 stringValue+=0x09;
663 p++;
664 break;
665 case 'v':
666 stringValue+=0x0b;
667 p++;
668 break;
669 case '0':
670 case '1':
671 case '2':
672 case '3':
673 case '4':
674 case '5':
675 case '6':
676 case '7':
677 {
678 int i=0;
679 for (i=0;i<3;i++) {
680 if (p+i>=rawString.length() ||
681 rawString[p+i]<'0' || rawString[p+i]>'7')
682 break;
683 }
684 bool ok;
685 unsigned char ch = rawString.mid(p,i).toInt(&ok,8);
686 stringValue+=ch;
687 p+=i;
688 break;
689 }
690 }
691 } else {
692 if (ch!='\"')
693 stringValue+=ch;
694 p++;
695 }
696 }
697 return QString::fromUtf8(stringValue);
698}
699
700GitError::GitError(const QString &reason):BaseError(reason)
701{
702
703}
704