2 Author: Marco Costalba (C) 2005-2007
4 Copyright: See COPYING file that comes with this distribution
7 Definitions of complex namespace constants
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.
20 #include <QTemporaryFile>
21 #include <QTextStream>
28 #ifdef Q_OS_WIN32 // ********* platform dependent code ******
30 const QString
QGit::SCRIPT_EXT
= ".bat";
32 static void adjustPath(QStringList
& args
, bool* winShell
) {
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.
40 if (args
.first() == "git" || args
.first().startsWith("git-")) {
42 if (!QGit::GIT_DIR
.isEmpty()) // application built from sources
43 args
.first().prepend(QGit::GIT_DIR
+ '/');
48 } else if (winShell
) {
50 args
.prepend("cmd.exe");
55 #elif defined(Q_OS_MACX) // MacOS X specific code
57 #include <sys/types.h> // used by chmod()
58 #include <sys/stat.h> // used by chmod()
60 const QString
QGit::SCRIPT_EXT
= ".sh";
62 static void adjustPath(QStringList
& args
, bool*) {
64 Under MacOS X, git typically doesn't live in the PATH
65 So use GIT_DIR from the settings if available
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
72 if (args
.first() == "git" || args
.first().startsWith("git-")) {
74 if (!QGit::GIT_DIR
.isEmpty()) // application built from sources
75 args
.first().prepend(QGit::GIT_DIR
+ '/');
82 #include <sys/types.h> // used by chmod()
83 #include <sys/stat.h> // used by chmod()
85 const QString
QGit::SCRIPT_EXT
= ".sh";
87 static void adjustPath(QStringList
&, bool*) {}
89 #endif // ********* end of platform dependent code ******
91 // definition of an optimized sha hash function
92 static inline uint
hexVal(const uchar
* ch
) {
94 return (*ch
< 64 ? *ch
- 48 : *ch
- 87);
97 uint
qHash(const ShaString
& s
) { // fast path, called 6-7 times per revision
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)
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
113 const ShaString
QGit::toTempSha(const QString
& sha
) {
115 static QByteArray ba
;
117 return ShaString(sha
.isEmpty() ? NULL
: ba
.constData());
120 const ShaString
QGit::toPersistentSha(const QString
& sha
, QVector
<QByteArray
>& v
) {
122 v
.append(sha
.toLatin1());
123 return ShaString(v
.last().constData());
126 // minimum git version required
127 const QString
QGit::GIT_VERSION
= "1.5.5";
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);
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
;
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
148 QFont
QGit::STD_FONT("Helvetica");
149 QFont
QGit::TYPE_WRITER_FONT("Helvetica");
151 // patches drag and drop
152 const QString
QGit::PATCHES_DIR
= "/.qgit_patches_copy";
153 const QString
QGit::PATCHES_NAME
= "qgit_import";
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";
160 const QByteArray
QGit::ZERO_SHA_BA(QGit::ZERO_SHA
.toLatin1());
161 const ShaString
QGit::ZERO_SHA_RAW(QGit::ZERO_SHA_BA
.constData());
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::DCLICK_ACT_KEY
= "double_click_action";
168 const QString
QGit::EXT_DIFF_KEY
= "external_diff_viewer";
169 const QString
QGit::EXT_EDITOR_KEY
= "external_editor";
170 const QString
QGit::REC_REP_KEY
= "recent_open_repos";
171 const QString
QGit::STD_FNT_KEY
= "standard_font";
172 const QString
QGit::TYPWRT_FNT_KEY
= "typewriter_font";
173 const QString
QGit::FLAGS_KEY
= "flags";
174 const QString
QGit::PATCH_DIR_KEY
= "Patch/last_dir";
175 const QString
QGit::FMT_P_OPT_KEY
= "Patch/args";
176 const QString
QGit::AM_P_OPT_KEY
= "Patch/args_2";
177 const QString
QGit::EX_KEY
= "Working_dir/exclude_file_path";
178 const QString
QGit::EX_PER_DIR_KEY
= "Working_dir/exclude_per_directory_file_name";
179 const QString
QGit::CON_GEOM_KEY
= "Console/geometry";
180 const QString
QGit::CMT_GEOM_KEY
= "Commit/geometry";
181 const QString
QGit::MAIN_GEOM_KEY
= "Top_window/geometry";
182 const QString
QGit::REV_GEOM_KEY
= "Rev_List_view/geometry";
183 const QString
QGit::REV_COLS_KEY
= "Rev_List_view/columns";
184 const QString
QGit::FILE_COLS_KEY
= "File_List_view/columns";
185 const QString
QGit::CMT_TEMPL_KEY
= "Commit/template_file_path";
186 const QString
QGit::CMT_ARGS_KEY
= "Commit/args";
187 const QString
QGit::RANGE_FROM_KEY
= "RangeSelect/from";
188 const QString
QGit::RANGE_TO_KEY
= "RangeSelect/to";
189 const QString
QGit::RANGE_OPT_KEY
= "RangeSelect/options";
190 const QString
QGit::ACT_GEOM_KEY
= "Custom_actions/geometry";
191 const QString
QGit::ACT_LIST_KEY
= "Custom_actions/list";
192 const QString
QGit::ACT_GROUP_KEY
= "Custom_action_list/";
193 const QString
QGit::ACT_TEXT_KEY
= "/commands";
194 const QString
QGit::ACT_FLAGS_KEY
= "/flags";
196 // settings default values
197 const QString
QGit::CMT_TEMPL_DEF
= ".git/commit-template";
198 const QString
QGit::EX_DEF
= ".git/info/exclude";
199 const QString
QGit::EX_PER_DIR_DEF
= ".gitignore";
200 const QString
QGit::EXT_DIFF_DEF
= "kompare";
201 const QString
QGit::EXT_EDITOR_DEF
= "emacs";
204 const QString
QGit::BAK_EXT
= ".bak";
205 const QString
QGit::C_DAT_FILE
= "/qgit_cache.dat";
208 const QString
QGit::QUOTE_CHAR
= "$";
211 using namespace QGit
;
214 uint
QGit::flags(SCRef flagsVariable
) {
217 return settings
.value(flagsVariable
, FLAGS_DEF
).toUInt();
220 bool QGit::testFlag(uint f
, SCRef flagsVariable
) {
222 return (flags(flagsVariable
) & f
);
225 void QGit::setFlag(uint f
, bool b
, SCRef flagsVariable
) {
228 uint flags
= settings
.value(flagsVariable
, FLAGS_DEF
).toUInt();
229 flags
= b
? flags
| f
: flags
& ~f
;
230 settings
.setValue(flagsVariable
, flags
);
233 // tree view icons helpers
234 static QHash
<QString
, const QPixmap
*> mimePixMap
;
236 void QGit::initMimePix() {
238 if (!mimePixMap
.empty()) // only once
241 QPixmap
* pm
= new QPixmap(QString::fromUtf8(":/icons/resources/folder.svg"));
242 mimePixMap
.insert("#folder_closed", pm
);
243 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/folder-open.svg"));
244 mimePixMap
.insert("#folder_open", pm
);
245 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/snap-page.svg"));
246 mimePixMap
.insert("#default", pm
);
247 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/text-x-cmake.svg"));
248 mimePixMap
.insert("CMakeLists.txt", pm
);
249 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/text-dockerfile.svg"));
250 mimePixMap
.insert("Dockerfile", pm
);
251 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/text-csv.svg"));
252 mimePixMap
.insert("csv", pm
);
253 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/text-x-csrc.svg"));
254 mimePixMap
.insert("c", pm
);
255 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/text-x-c++src.svg"));
256 mimePixMap
.insert("cpp", pm
);
257 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/text-x-chdr.svg"));
258 mimePixMap
.insert("h", pm
);
259 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/text-x-c++hdr.svg"));
260 mimePixMap
.insert("hpp", pm
);
261 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/text-x-generic.svg"));
262 mimePixMap
.insert("txt", pm
);
263 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/text-rtf.svg"));
264 mimePixMap
.insert("rtf", pm
);
265 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/text-x-script.svg"));
266 mimePixMap
.insert("sh", pm
);
267 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/application-x-perl.svg"));
268 mimePixMap
.insert("perl", pm
);
269 pm
= new QPixmap(*pm
);
270 mimePixMap
.insert("pl", pm
);
271 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/application-x-python-bytecode.svg"));
272 mimePixMap
.insert("py", pm
);
273 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/application-x-java.svg"));
274 mimePixMap
.insert("java", pm
);
275 pm
= new QPixmap(*pm
);
276 mimePixMap
.insert("jar", pm
);
277 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/application-x-tar.svg"));
278 mimePixMap
.insert("tar", pm
);
279 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/application-x-ace.svg"));
280 mimePixMap
.insert("gz", pm
);
281 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/application-x-compressed-tar.svg"));
282 mimePixMap
.insert("tgz", pm
);
283 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/application-zip.svg"));
284 mimePixMap
.insert("zip", pm
);
285 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/application-x-bzip.svg"));
286 mimePixMap
.insert("bz", pm
);
287 pm
= new QPixmap(*pm
);
288 mimePixMap
.insert("bz2", pm
);
289 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/text-html.svg"));
290 mimePixMap
.insert("html", pm
);
291 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/dialog-xml-editor.svg"));
292 mimePixMap
.insert("xml", pm
);
293 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/image-bmp.svg"));
294 mimePixMap
.insert("bmp", pm
);
295 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/image-gif.svg"));
296 mimePixMap
.insert("gif", pm
);
297 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/image-jpeg.svg"));
298 mimePixMap
.insert("jpg", pm
);
299 pm
= new QPixmap(*pm
);
300 mimePixMap
.insert("jpeg", pm
);
301 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/image-png.svg"));
302 mimePixMap
.insert("png", pm
);
303 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/image-svg+xml-compressed.svg"));
304 mimePixMap
.insert("svg", pm
);
305 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/image-tiff.svg"));
306 mimePixMap
.insert("tiff", pm
);
307 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/image-x-ico.svg"));
308 mimePixMap
.insert("ico", pm
);
309 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/image-x-xcf.svg"));
310 mimePixMap
.insert("xcf", pm
);
311 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/image-x-generic.svg"));
312 mimePixMap
.insert("pbm", pm
);
313 pm
= new QPixmap(*pm
);
314 mimePixMap
.insert("pgm", pm
);
315 pm
= new QPixmap(*pm
);
316 mimePixMap
.insert("ppm", pm
);
317 pm
= new QPixmap(*pm
);
318 mimePixMap
.insert("xbm", pm
);
319 pm
= new QPixmap(*pm
);
320 mimePixMap
.insert("xpm", pm
);
321 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/application-json.svg"));
322 mimePixMap
.insert("json", pm
);
323 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/application-pdf.svg"));
324 mimePixMap
.insert("pdf", pm
);
325 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/application-x-javascript.svg"));
326 mimePixMap
.insert("js", pm
);
327 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/text-x-go.svg"));
328 mimePixMap
.insert("go", pm
);
329 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/text-x-markdown.svg"));
330 mimePixMap
.insert("md", pm
);
331 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/text-x-patch.svg"));
332 mimePixMap
.insert("patch", pm
);
333 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/text-x-qml.svg"));
334 mimePixMap
.insert("qml", pm
);
335 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/text-x-tex.svg"));
336 mimePixMap
.insert("tex", pm
);
337 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/application-x-ruby.svg"));
338 mimePixMap
.insert("rb", pm
);
339 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/text-rust.svg"));
340 mimePixMap
.insert("rs", pm
);
341 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/text-css.svg"));
342 mimePixMap
.insert("css", pm
);
343 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/text-x-csharp.svg"));
344 mimePixMap
.insert("cs", pm
);
345 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/text-x-r.svg"));
346 mimePixMap
.insert("r", pm
);
347 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/application-x-designer.svg"));
348 mimePixMap
.insert("ui", pm
);
349 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/project-development.svg"));
350 mimePixMap
.insert("pro", pm
);
351 pm
= new QPixmap(*pm
);
352 mimePixMap
.insert("sln", pm
);
353 pm
= new QPixmap(*pm
);
354 mimePixMap
.insert("vcproj", pm
);
355 pm
= new QPixmap(*pm
);
356 mimePixMap
.insert("vcxproj", pm
);
357 pm
= new QPixmap(QString::fromUtf8(":/icons/resources/file/application-x-sharedlib.svg"));
358 mimePixMap
.insert("Makefile", pm
);
361 void QGit::freeMimePix() {
363 qDeleteAll(mimePixMap
);
366 const QPixmap
* QGit::mimePix(SCRef fileName
) {
368 // Try to match full filename (e.g. CMakeLists.txt)
369 if (mimePixMap
.contains(fileName
))
370 return mimePixMap
.value(fileName
);
372 // Try to match extension
373 SCRef ext
= fileName
.section('.', -1, -1).toLower();
374 if (mimePixMap
.contains(ext
))
375 return mimePixMap
.value(ext
);
377 return mimePixMap
.value("#default");
380 // geometry settings helers
381 void QGit::saveGeometrySetting(SCRef name
, QWidget
* w
, splitVect
* svPtr
) {
384 if (w
&& w
->isVisible())
385 settings
.setValue(name
+ "_window", w
->saveGeometry());
391 FOREACH (splitVect
, it
, *svPtr
) {
394 if ((*it
)->sizes().contains(0))
397 QString
nm(name
+ "_splitter_" + QString::number(cnt
));
398 settings
.setValue(nm
, (*it
)->saveState());
402 void QGit::restoreGeometrySetting(SCRef name
, QWidget
* w
, splitVect
* svPtr
) {
407 nm
= name
+ "_window";
408 QVariant v
= settings
.value(nm
);
410 w
->restoreGeometry(v
.toByteArray());
416 FOREACH (splitVect
, it
, *svPtr
) {
419 nm
= name
+ "_splitter_" + QString::number(cnt
);
420 QVariant v
= settings
.value(nm
);
424 (*it
)->restoreState(v
.toByteArray());
429 bool QGit::stripPartialParaghraps(const QByteArray
& ba
, QString
* dst
, QString
* prev
) {
431 QTextCodec
* tc
= QTextCodec::codecForLocale();
433 if (ba
.endsWith('\n')) { // optimize common case
434 *dst
= tc
->toUnicode(ba
);
436 // handle rare case of a '\0' inside content
437 while (dst
->size() < ba
.size() && ba
.at(dst
->size()) == '\0') {
438 QString s
= tc
->toUnicode(ba
.mid(dst
->size() + 1)); // sizes should match
439 dst
->append(" ").append(s
);
442 dst
->truncate(dst
->size() - 1); // strip trailing '\n'
443 if (!prev
->isEmpty()) {
449 QString src
= tc
->toUnicode(ba
);
450 // handle rare case of a '\0' inside content
451 while (src
.size() < ba
.size() && ba
.at(src
.size()) == '\0') {
452 QString s
= tc
->toUnicode(ba
.mid(src
.size() + 1));
453 src
.append(" ").append(s
);
456 int idx
= src
.lastIndexOf('\n');
462 *dst
= src
.left(idx
).prepend(*prev
); // strip trailing '\n'
463 *prev
= src
.mid(idx
+ 1); // src[idx] is '\n', skip it
467 bool QGit::writeToFile(SCRef fileName
, SCRef data
, bool setExecutable
) {
469 QFile
file(fileName
);
470 if (!file
.open(QIODevice::WriteOnly
)) {
471 dbp("ERROR: unable to write file %1", fileName
);
475 QTextStream
stream(&file
);
478 data2
.replace("\r\n", "\n"); // change windows CRLF to linux
479 data2
.replace("\n", "\r\n"); // then change all linux CRLF to windows
486 chmod(fileName
.toLatin1().constData(), 0755);
491 bool QGit::writeToFile(SCRef fileName
, const QByteArray
& data
, bool setExecutable
) {
493 QFile
file(fileName
);
494 if (!file
.open(QIODevice::WriteOnly
)) {
495 dbp("ERROR: unable to write file %1", fileName
);
498 QDataStream
stream(&file
);
499 stream
.writeRawData(data
.constData(), data
.size());
504 chmod(fileName
.toLatin1().constData(), 0755);
509 bool QGit::readFromFile(SCRef fileName
, QString
& data
) {
512 QFile
file(fileName
);
513 if (!file
.open(QIODevice::ReadOnly
)) {
514 dbp("ERROR: unable to read file %1", fileName
);
517 QTextStream
stream(&file
);
518 data
= stream
.readAll();
523 bool QGit::startProcess(QProcess
* proc
, SCList args
, SCRef buf
, bool* winShell
) {
525 if (!proc
|| args
.isEmpty())
528 QStringList
arguments(args
);
529 adjustPath(arguments
, winShell
);
531 QString
prog(arguments
.first());
532 arguments
.removeFirst();
533 if (!buf
.isEmpty()) {
535 On Windows buffer size of QProcess's standard input
536 pipe is quite limited and a crash can occur in case
537 a big chunk of data is written to process stdin.
538 As a workaround we use a temporary file to store data.
539 Process stdin will be redirected to this file
541 QTemporaryFile
* bufFile
= new QTemporaryFile(proc
);
543 QTextStream
stream(bufFile
);
545 proc
->setStandardInputFile(bufFile
->fileName());
548 QStringList env
= QProcess::systemEnvironment();
549 env
<< "GIT_TRACE=0"; // avoid choking on debug traces
550 env
<< "GIT_FLUSH=0"; // skip the fflush() in 'git log'
551 proc
->setEnvironment(env
);
553 proc
->start(prog
, arguments
); // TODO test QIODevice::Unbuffered
554 return proc
->waitForStarted();