1 | #include "gitmanager.h" |
2 | #include "../utils.h" |
3 | #include "../settings.h" |
4 | |
5 | #include <QDir> |
6 | #include <QFileInfo> |
7 | |
8 | GitManager::GitManager(QObject *parent) : QObject(parent) |
9 | { |
10 | } |
11 | |
12 | void 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 | |
36 | bool 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 | |
60 | QString 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 | |
68 | bool 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 | |
77 | bool 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 | |
88 | bool 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 | |
98 | bool 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 | |
107 | bool 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 | |
116 | bool 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 | |
127 | bool 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 | |
139 | int 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 | |
156 | QList<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 | |
198 | QStringList GitManager::listFiles(const QString &folder) |
199 | { |
200 | QStringList args; |
201 | args.append("ls-files" ); |
202 | return textToLines(runGit(folder,args)); |
203 | } |
204 | |
205 | QStringList 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 | |
214 | QStringList 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 | |
222 | QStringList 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 | |
231 | QStringList GitManager::listRemotes(const QString &folder) |
232 | { |
233 | QStringList args; |
234 | args.append("remote" ); |
235 | return textToLines(runGit(folder,args)); |
236 | } |
237 | |
238 | bool 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 | |
249 | bool 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 | |
261 | bool 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 | |
273 | bool 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 | |
285 | QString 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 | |
294 | QString 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 | |
303 | QString 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 | |
312 | bool 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 | |
326 | bool 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 | |
334 | bool 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 | |
342 | bool 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 | |
350 | bool 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 | |
361 | bool 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 | |
371 | bool 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 | |
383 | bool GitManager::setUserName(const QString &folder, const QString &userName, QString &output) |
384 | { |
385 | return setConfig(folder,"user.name" ,userName,output); |
386 | } |
387 | |
388 | bool GitManager::setUserEmail(const QString &folder, const QString &userEmail, QString &output) |
389 | { |
390 | return setConfig(folder,"user.email" ,userEmail,output); |
391 | } |
392 | |
393 | QString 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 | |
402 | QString GitManager::getUserName(const QString& folder) |
403 | { |
404 | return getConfig(folder, "user.name" ); |
405 | } |
406 | |
407 | QString GitManager::getUserEmail(const QString& folder) |
408 | { |
409 | return getConfig(folder, "user.email" ); |
410 | } |
411 | |
412 | QStringList GitManager::listBranches(const QString &folder, int ¤t) |
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 | |
434 | bool 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 | |
457 | bool 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 | |
482 | bool 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 | |
492 | void GitManager::abortMerge(const QString &folder) |
493 | { |
494 | QStringList args; |
495 | args.append("merge" ); |
496 | args.append("--abort" ); |
497 | runGit(folder,args); |
498 | } |
499 | |
500 | bool 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 | |
514 | bool 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 | |
523 | bool 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 | |
535 | bool 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 | |
543 | bool 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 | |
572 | bool GitManager::isValid() |
573 | { |
574 | return pSettings->vcs().gitOk(); |
575 | } |
576 | |
577 | QString 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 | |
615 | QString 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 | |
700 | GitError::GitError(const QString &reason):BaseError(reason) |
701 | { |
702 | |
703 | } |
704 | |