1 /*
2 Author: Marco Costalba (C) 2005-2007
3
4 Copyright: See COPYING file that comes with this distribution
5
6
7 Definitions of complex namespace constants
8
9 Complex constant objects are not folded in like integral types, so they
10 are declared 'extern' in namespace to avoid duplicating them as file scope
11 data in each file where QGit namespace is included.
12
13 */
14 #include <QDir>
15 #include <QHash>
16 #include <QPixmap>
17 #include <QProcess>
18 #include <QSettings>
19 #include <QSplitter>
20 #include <QTemporaryFile>
21 #include <QTextStream>
22 #include <QWidget>
23 #include <QTextCodec>
24 #include "common.h"
25 #include "git.h"
26 #include "annotate.h"
27
28 #ifdef Q_OS_WIN32 // ********* platform dependent code ******
29
30 const QString QGit::SCRIPT_EXT = ".bat";
31
adjustPath(QStringList & args,bool * winShell)32 static void adjustPath(QStringList& args, bool* winShell) {
33 /*
34 To run an application/script under Windows you need
35 to wrap the command line in the shell interpreter.
36 You need this also to start native commands as 'dir'.
37 An exception is if application is 'git' in that case we
38 call with absolute path to be sure to find it.
39 */
40 if (args.first() == "git" || args.first().startsWith("git-")) {
41
42 if (!QGit::GIT_DIR.isEmpty()) // application built from sources
43 args.first().prepend(QGit::GIT_DIR + '/');
44
45 if (winShell)
46 *winShell = false;
47
48 } else if (winShell) {
49 args.prepend("/c");
50 args.prepend("cmd.exe");
51 *winShell = true;
52 }
53 }
54
55 #elif defined(Q_OS_MACX) // MacOS X specific code
56
57 #include <sys/types.h> // used by chmod()
58 #include <sys/stat.h> // used by chmod()
59
60 const QString QGit::SCRIPT_EXT = ".sh";
61
adjustPath(QStringList & args,bool *)62 static void adjustPath(QStringList& args, bool*) {
63 /*
64 Under MacOS X, git typically doesn't live in the PATH
65 So use GIT_DIR from the settings if available
66
67 Note: I (OC) think that this should be the default behaviour,
68 but I don't want to break other platforms, so I introduced
69 the MacOS X special case. Feel free to make this the default if
70 you do feel the same.
71 */
72 if (args.first() == "git" || args.first().startsWith("git-")) {
73
74 if (!QGit::GIT_DIR.isEmpty()) // application built from sources
75 args.first().prepend(QGit::GIT_DIR + '/');
76
77 }
78 }
79
80 #else
81
82 #include <sys/types.h> // used by chmod()
83 #include <sys/stat.h> // used by chmod()
84
85 const QString QGit::SCRIPT_EXT = ".sh";
86
adjustPath(QStringList &,bool *)87 static void adjustPath(QStringList&, bool*) {}
88
89 #endif // ********* end of platform dependent code ******
90
91 // definition of an optimized sha hash function
hexVal(const uchar * ch)92 static inline uint hexVal(const uchar* ch) {
93
94 return (*ch < 64 ? *ch - 48 : *ch - 87);
95 }
96
qHash(const ShaString & s)97 uint qHash(const ShaString& s) { // fast path, called 6-7 times per revision
98
99 const uchar* ch = reinterpret_cast<const uchar*>(s.latin1());
100 return (hexVal(ch ) << 24)
101 + (hexVal(ch + 2) << 20)
102 + (hexVal(ch + 4) << 16)
103 + (hexVal(ch + 6) << 12)
104 + (hexVal(ch + 8) << 8)
105 + (hexVal(ch + 10) << 4)
106 + hexVal(ch + 12);
107 }
108
109 /* Value returned by this function should be used only as function argument,
110 * and not stored in a variable because 'ba' value is overwritten at each
111 * call so the returned ShaString could became stale very quickly
112 */
toTempSha(const QString & sha)113 const ShaString QGit::toTempSha(const QString& sha) {
114
115 static QByteArray ba;
116 ba = sha.toLatin1();
117 return ShaString(sha.isEmpty() ? NULL : ba.constData());
118 }
119
toPersistentSha(const QString & sha,QVector<QByteArray> & v)120 const ShaString QGit::toPersistentSha(const QString& sha, QVector<QByteArray>& v) {
121
122 v.append(sha.toLatin1());
123 return ShaString(v.last().constData());
124 }
125
126 // minimum git version required
127 const QString QGit::GIT_VERSION = "1.5.5";
128
129 // colors
130 const QColor QGit::BROWN = QColor(150, 75, 0);
131 const QColor QGit::ORANGE = QColor(255, 160, 50);
132 const QColor QGit::DARK_ORANGE = QColor(216, 144, 0);
133 const QColor QGit::LIGHT_ORANGE = QColor(255, 221, 170);
134 const QColor QGit::LIGHT_BLUE = QColor(85, 255, 255);
135 const QColor QGit::PURPLE = QColor(221, 221, 255);
136 const QColor QGit::DARK_GREEN = QColor(0, 205, 0);
137
138 // initialized at startup according to system wide settings
139 QColor QGit::ODD_LINE_COL;
140 QColor QGit::EVEN_LINE_COL;
141 QString QGit::GIT_DIR;
142
143 /*
144 Default QFont c'tor calls static method QApplication::font() that could
145 be still NOT initialized at this time, so set a dummy font family instead,
146 it will be properly changed later, at startup
147 */
148 QFont QGit::STD_FONT("Helvetica");
149 QFont QGit::TYPE_WRITER_FONT("Helvetica");
150
151 // patches drag and drop
152 const QString QGit::PATCHES_DIR = "/.qgit_patches_copy";
153 const QString QGit::PATCHES_NAME = "qgit_import";
154
155 // git index parameters
156 const QString QGit::ZERO_SHA = "0000000000000000000000000000000000000000";
157 const QString QGit::CUSTOM_SHA = "*** CUSTOM * CUSTOM * CUSTOM * CUSTOM **";
158 const QString QGit::ALL_MERGE_FILES = "ALL_MERGE_FILES";
159
160 const QByteArray QGit::ZERO_SHA_BA(QGit::ZERO_SHA.toLatin1());
161 const ShaString QGit::ZERO_SHA_RAW(QGit::ZERO_SHA_BA.constData());
162
163 // settings keys
164 const QString QGit::ORG_KEY = "qgit";
165 const QString QGit::APP_KEY = "qgit4";
166 const QString QGit::GIT_DIR_KEY = "msysgit_exec_dir";
167 const QString QGit::EXT_DIFF_KEY = "external_diff_viewer";
168 const QString QGit::EXT_EDITOR_KEY = "external_editor";
169 const QString QGit::REC_REP_KEY = "recent_open_repos";
170 const QString QGit::STD_FNT_KEY = "standard_font";
171 const QString QGit::TYPWRT_FNT_KEY = "typewriter_font";
172 const QString QGit::FLAGS_KEY = "flags";
173 const QString QGit::PATCH_DIR_KEY = "Patch/last_dir";
174 const QString QGit::FMT_P_OPT_KEY = "Patch/args";
175 const QString QGit::AM_P_OPT_KEY = "Patch/args_2";
176 const QString QGit::EX_KEY = "Working_dir/exclude_file_path";
177 const QString QGit::EX_PER_DIR_KEY = "Working_dir/exclude_per_directory_file_name";
178 const QString QGit::CON_GEOM_KEY = "Console/geometry";
179 const QString QGit::CMT_GEOM_KEY = "Commit/geometry";
180 const QString QGit::MAIN_GEOM_KEY = "Top_window/geometry";
181 const QString QGit::REV_GEOM_KEY = "Rev_List_view/geometry";
182 const QString QGit::CMT_TEMPL_KEY = "Commit/template_file_path";
183 const QString QGit::CMT_ARGS_KEY = "Commit/args";
184 const QString QGit::RANGE_FROM_KEY = "RangeSelect/from";
185 const QString QGit::RANGE_TO_KEY = "RangeSelect/to";
186 const QString QGit::RANGE_OPT_KEY = "RangeSelect/options";
187 const QString QGit::ACT_GEOM_KEY = "Custom_actions/geometry";
188 const QString QGit::ACT_LIST_KEY = "Custom_actions/list";
189 const QString QGit::ACT_GROUP_KEY = "Custom_action_list/";
190 const QString QGit::ACT_TEXT_KEY = "/commands";
191 const QString QGit::ACT_FLAGS_KEY = "/flags";
192
193 // settings default values
194 const QString QGit::CMT_TEMPL_DEF = ".git/commit-template";
195 const QString QGit::EX_DEF = ".git/info/exclude";
196 const QString QGit::EX_PER_DIR_DEF = ".gitignore";
197 const QString QGit::EXT_DIFF_DEF = "kompare";
198 const QString QGit::EXT_EDITOR_DEF = "emacs";
199
200 // cache file
201 const QString QGit::BAK_EXT = ".bak";
202 const QString QGit::C_DAT_FILE = "/qgit_cache.dat";
203
204 // misc
205 const QString QGit::QUOTE_CHAR = "$";
206
207
208 using namespace QGit;
209
210 // settings helpers
flags(SCRef flagsVariable)211 uint QGit::flags(SCRef flagsVariable) {
212
213 QSettings settings;
214 return settings.value(flagsVariable, FLAGS_DEF).toUInt();
215 }
216
testFlag(uint f,SCRef flagsVariable)217 bool QGit::testFlag(uint f, SCRef flagsVariable) {
218
219 return (flags(flagsVariable) & f);
220 }
221
setFlag(uint f,bool b,SCRef flagsVariable)222 void QGit::setFlag(uint f, bool b, SCRef flagsVariable) {
223
224 QSettings settings;
225 uint flags = settings.value(flagsVariable, FLAGS_DEF).toUInt();
226 flags = b ? flags | f : flags & ~f;
227 settings.setValue(flagsVariable, flags);
228 }
229
230 // tree view icons helpers
231 static QHash<QString, const QPixmap*> mimePixMap;
232
initMimePix()233 void QGit::initMimePix() {
234
235 if (!mimePixMap.empty()) // only once
236 return;
237
238 QPixmap* pm = new QPixmap(QString::fromUtf8(":/icons/resources/folder.png"));
239 mimePixMap.insert("#folder_closed", pm);
240 pm = new QPixmap(QString::fromUtf8(":/icons/resources/folder_open.png"));
241 mimePixMap.insert("#folder_open", pm);
242 pm = new QPixmap(QString::fromUtf8(":/icons/resources/misc.png"));
243 mimePixMap.insert("#default", pm);
244 pm = new QPixmap(QString::fromUtf8(":/icons/resources/source_c.png"));
245 mimePixMap.insert("c", pm);
246 pm = new QPixmap(QString::fromUtf8(":/icons/resources/source_cpp.png"));
247 mimePixMap.insert("cpp", pm);
248 pm = new QPixmap(QString::fromUtf8(":/icons/resources/source_h.png"));
249 mimePixMap.insert("h", pm);
250 pm = new QPixmap(*pm);
251 mimePixMap.insert("hpp", pm);
252 pm = new QPixmap(QString::fromUtf8(":/icons/resources/txt.png"));
253 mimePixMap.insert("txt", pm);
254 pm = new QPixmap(QString::fromUtf8(":/icons/resources/shellscript.png"));
255 mimePixMap.insert("sh", pm);
256 pm = new QPixmap(QString::fromUtf8(":/icons/resources/source_pl.png"));
257 mimePixMap.insert("perl", pm);
258 pm = new QPixmap(*pm);
259 mimePixMap.insert("pl", pm);
260 pm = new QPixmap(QString::fromUtf8(":/icons/resources/source_py.png"));
261 mimePixMap.insert("py", pm);
262 pm = new QPixmap(QString::fromUtf8(":/icons/resources/source_java.png"));
263 mimePixMap.insert("java", pm);
264 pm = new QPixmap(*pm);
265 mimePixMap.insert("jar", pm);
266 pm = new QPixmap(QString::fromUtf8(":/icons/resources/tar.png"));
267 mimePixMap.insert("tar", pm);
268 pm = new QPixmap(*pm);
269 mimePixMap.insert("gz", pm);
270 pm = new QPixmap(*pm);
271 mimePixMap.insert("tgz", pm);
272 pm = new QPixmap(*pm);
273 mimePixMap.insert("zip", pm);
274 pm = new QPixmap(*pm);
275 mimePixMap.insert("bz", pm);
276 pm = new QPixmap(*pm);
277 mimePixMap.insert("bz2", pm);
278 pm = new QPixmap(QString::fromUtf8(":/icons/resources/html.png"));
279 mimePixMap.insert("html", pm);
280 pm = new QPixmap(*pm);
281 mimePixMap.insert("xml", pm);
282 pm = new QPixmap(QString::fromUtf8(":/icons/resources/image.png"));
283 mimePixMap.insert("bmp", pm);
284 pm = new QPixmap(*pm);
285 mimePixMap.insert("gif", pm);
286 pm = new QPixmap(*pm);
287 mimePixMap.insert("jpg", pm);
288 pm = new QPixmap(*pm);
289 mimePixMap.insert("jpeg", pm);
290 pm = new QPixmap(*pm);
291 mimePixMap.insert("png", pm);
292 pm = new QPixmap(*pm);
293 mimePixMap.insert("pbm", pm);
294 pm = new QPixmap(*pm);
295 mimePixMap.insert("pgm", pm);
296 pm = new QPixmap(*pm);
297 mimePixMap.insert("ppm", pm);
298 pm = new QPixmap(*pm);
299 mimePixMap.insert("svg", pm);
300 pm = new QPixmap(*pm);
301 mimePixMap.insert("tiff", pm);
302 pm = new QPixmap(*pm);
303 mimePixMap.insert("xbm", pm);
304 pm = new QPixmap(*pm);
305 mimePixMap.insert("xpm", pm);
306 }
307
freeMimePix()308 void QGit::freeMimePix() {
309
310 qDeleteAll(mimePixMap);
311 }
312
mimePix(SCRef fileName)313 const QPixmap* QGit::mimePix(SCRef fileName) {
314
315 SCRef ext = fileName.section('.', -1, -1).toLower();
316 if (mimePixMap.contains(ext))
317 return mimePixMap.value(ext);
318
319 return mimePixMap.value("#default");
320 }
321
322 // geometry settings helers
saveGeometrySetting(SCRef name,QWidget * w,splitVect * svPtr)323 void QGit::saveGeometrySetting(SCRef name, QWidget* w, splitVect* svPtr) {
324
325 QSettings settings;
326 if (w && w->isVisible())
327 settings.setValue(name + "_window", w->saveGeometry());
328
329 if (!svPtr)
330 return;
331
332 int cnt = 0;
333 FOREACH (splitVect, it, *svPtr) {
334
335 cnt++;
336 if ((*it)->sizes().contains(0))
337 continue;
338
339 QString nm(name + "_splitter_" + QString::number(cnt));
340 settings.setValue(nm, (*it)->saveState());
341 }
342 }
343
restoreGeometrySetting(SCRef name,QWidget * w,splitVect * svPtr)344 void QGit::restoreGeometrySetting(SCRef name, QWidget* w, splitVect* svPtr) {
345
346 QSettings settings;
347 QString nm;
348 if (w) {
349 nm = name + "_window";
350 QVariant v = settings.value(nm);
351 if (v.isValid())
352 w->restoreGeometry(v.toByteArray());
353 }
354 if (!svPtr)
355 return;
356
357 int cnt = 0;
358 FOREACH (splitVect, it, *svPtr) {
359
360 cnt++;
361 nm = name + "_splitter_" + QString::number(cnt);
362 QVariant v = settings.value(nm);
363 if (!v.isValid())
364 continue;
365
366 (*it)->restoreState(v.toByteArray());
367 }
368 }
369
370 // misc helpers
stripPartialParaghraps(const QByteArray & ba,QString * dst,QString * prev)371 bool QGit::stripPartialParaghraps(const QByteArray& ba, QString* dst, QString* prev) {
372
373 QTextCodec* tc = QTextCodec::codecForLocale();
374
375 if (ba.endsWith('\n')) { // optimize common case
376 *dst = tc->toUnicode(ba);
377
378 // handle rare case of a '\0' inside content
379 while (dst->size() < ba.size() && ba.at(dst->size()) == '\0') {
380 QString s = tc->toUnicode(ba.mid(dst->size() + 1)); // sizes should match
381 dst->append(" ").append(s);
382 }
383
384 dst->truncate(dst->size() - 1); // strip trailing '\n'
385 if (!prev->isEmpty()) {
386 dst->prepend(*prev);
387 prev->clear();
388 }
389 return true;
390 }
391 QString src = tc->toUnicode(ba);
392 // handle rare case of a '\0' inside content
393 while (src.size() < ba.size() && ba.at(src.size()) == '\0') {
394 QString s = tc->toUnicode(ba.mid(src.size() + 1));
395 src.append(" ").append(s);
396 }
397
398 int idx = src.lastIndexOf('\n');
399 if (idx == -1) {
400 prev->append(src);
401 dst->clear();
402 return false;
403 }
404 *dst = src.left(idx).prepend(*prev); // strip trailing '\n'
405 *prev = src.mid(idx + 1); // src[idx] is '\n', skip it
406 return true;
407 }
408
writeToFile(SCRef fileName,SCRef data,bool setExecutable)409 bool QGit::writeToFile(SCRef fileName, SCRef data, bool setExecutable) {
410
411 QFile file(fileName);
412 if (!file.open(QIODevice::WriteOnly)) {
413 dbp("ERROR: unable to write file %1", fileName);
414 return false;
415 }
416 QString data2(data);
417 QTextStream stream(&file);
418
419 #ifdef Q_OS_WIN32
420 data2.replace("\r\n", "\n"); // change windows CRLF to linux
421 data2.replace("\n", "\r\n"); // then change all linux CRLF to windows
422 #endif
423 stream << data2;
424 file.close();
425
426 #ifndef Q_OS_WIN32
427 if (setExecutable)
428 chmod(fileName.toLatin1().constData(), 0755);
429 #endif
430 return true;
431 }
432
writeToFile(SCRef fileName,const QByteArray & data,bool setExecutable)433 bool QGit::writeToFile(SCRef fileName, const QByteArray& data, bool setExecutable) {
434
435 QFile file(fileName);
436 if (!file.open(QIODevice::WriteOnly)) {
437 dbp("ERROR: unable to write file %1", fileName);
438 return false;
439 }
440 QDataStream stream(&file);
441 stream.writeRawData(data.constData(), data.size());
442 file.close();
443
444 #ifndef Q_OS_WIN32
445 if (setExecutable)
446 chmod(fileName.toLatin1().constData(), 0755);
447 #endif
448 return true;
449 }
450
readFromFile(SCRef fileName,QString & data)451 bool QGit::readFromFile(SCRef fileName, QString& data) {
452
453 data = "";
454 QFile file(fileName);
455 if (!file.open(QIODevice::ReadOnly)) {
456 dbp("ERROR: unable to read file %1", fileName);
457 return false;
458 }
459 QTextStream stream(&file);
460 data = stream.readAll();
461 file.close();
462 return true;
463 }
464
startProcess(QProcess * proc,SCList args,SCRef buf,bool * winShell)465 bool QGit::startProcess(QProcess* proc, SCList args, SCRef buf, bool* winShell) {
466
467 if (!proc || args.isEmpty())
468 return false;
469
470 QStringList arguments(args);
471 adjustPath(arguments, winShell);
472
473 QString prog(arguments.first());
474 arguments.removeFirst();
475 if (!buf.isEmpty()) {
476 /*
477 On Windows buffer size of QProcess's standard input
478 pipe is quite limited and a crash can occur in case
479 a big chunk of data is written to process stdin.
480 As a workaround we use a temporary file to store data.
481 Process stdin will be redirected to this file
482 */
483 QTemporaryFile* bufFile = new QTemporaryFile(proc);
484 bufFile->open();
485 QTextStream stream(bufFile);
486 stream << buf;
487 proc->setStandardInputFile(bufFile->fileName());
488 bufFile->close();
489 }
490 QStringList env = QProcess::systemEnvironment();
491 env << "GIT_TRACE=0"; // avoid choking on debug traces
492 env << "GIT_FLUSH=0"; // skip the fflush() in 'git log'
493 proc->setEnvironment(env);
494
495 proc->start(prog, arguments); // TODO test QIODevice::Unbuffered
496 return proc->waitForStarted();
497 }
498