# HG changeset patch # User Chris Cannam # Date 1308744110 -3600 # Node ID 2af4b5b0bf8340dd3e15ec94a64546c49c2803dd # Parent 498c2ca0b3677c86682e43962b189d60167fef5a# Parent 653e9694a69413cb140b035a45656b15a046d632 Merge from branch "ignore" diff -r 498c2ca0b367 -r 2af4b5b0bf83 .hgignore --- a/.hgignore Mon Jun 13 16:56:01 2011 +0100 +++ b/.hgignore Wed Jun 22 13:01:50 2011 +0100 @@ -20,3 +20,6 @@ *.app/* .DS_Store *.pdb + +re:^EasyMercurial +re:^kdiff3 diff -r 498c2ca0b367 -r 2af4b5b0bf83 easyhg.pro --- a/easyhg.pro Mon Jun 13 16:56:01 2011 +0100 +++ b/easyhg.pro Wed Jun 22 13:01:50 2011 +0100 @@ -4,6 +4,9 @@ TEMPLATE = app TARGET = EasyMercurial +QMAKE_CXX = clang++ +QMAKE_LINK = clang++ + # We use the 10.4 SDK and Carbon for all 32-bit OS/X, # and 10.6 with Cocoa for all 64-bit macx-g++40 { @@ -61,7 +64,8 @@ src/clickablelabel.h \ src/workstatuswidget.h \ src/moreinformationdialog.h \ - src/annotatedialog.h + src/annotatedialog.h \ + src/hgignoredialog.h SOURCES = \ src/main.cpp \ src/mainwindow.cpp \ @@ -95,7 +99,8 @@ src/settingsdialog.cpp \ src/workstatuswidget.cpp \ src/moreinformationdialog.cpp \ - src/annotatedialog.cpp + src/annotatedialog.cpp \ + src/hgignoredialog.cpp macx-* { SOURCES += src/common_osx.mm diff -r 498c2ca0b367 -r 2af4b5b0bf83 src/changeset.cpp --- a/src/changeset.cpp Mon Jun 13 16:56:01 2011 +0100 +++ b/src/changeset.cpp Wed Jun 22 13:01:50 2011 +0100 @@ -17,6 +17,7 @@ #include "changeset.h" #include "common.h" +#include "debug.h" #include @@ -53,6 +54,8 @@ description = ""; +// DEBUG << "comment is " << comment() << endl; + QString c = comment().trimmed(); c = c.replace(QRegExp("^\""), ""); c = c.replace(QRegExp("\"$"), ""); diff -r 498c2ca0b367 -r 2af4b5b0bf83 src/confirmcommentdialog.cpp --- a/src/confirmcommentdialog.cpp Mon Jun 13 16:56:01 2011 +0100 +++ b/src/confirmcommentdialog.cpp Wed Jun 22 13:01:50 2011 +0100 @@ -17,6 +17,7 @@ #include "confirmcommentdialog.h" #include "common.h" +#include "debug.h" #include #include @@ -67,7 +68,19 @@ QString ConfirmCommentDialog::getComment() const { - return m_textEdit->document()->toPlainText(); +/* + DEBUG << "ConfirmCommentDialog: as html is:" << endl; + DEBUG << m_textEdit->document()->toHtml() << endl; + DEBUG << "ConfirmCommentDialog: as plain text is:" << endl; +*/ + QString t = m_textEdit->document()->toPlainText(); +/* + DEBUG << t << endl; + DEBUG << "Characters: " << endl; + for (int i = 0; i < t.length(); ++i) DEBUG << t[i].unicode(); + DEBUG << endl; +*/ + return t; } QString ConfirmCommentDialog::buildFilesText(QString intro, QStringList files) diff -r 498c2ca0b367 -r 2af4b5b0bf83 src/filestates.cpp --- a/src/filestates.cpp Mon Jun 13 16:56:01 2011 +0100 +++ b/src/filestates.cpp Wed Jun 22 13:01:50 2011 +0100 @@ -137,6 +137,11 @@ return Unknown; } +bool FileStates::isKnown(QString file) const +{ + return (m_stateMap.contains(file)); +} + FileStates::Activities FileStates::activitiesSupportedBy(State s) { Activities a; diff -r 498c2ca0b367 -r 2af4b5b0bf83 src/filestates.h --- a/src/filestates.h Mon Jun 13 16:56:01 2011 +0100 +++ b/src/filestates.h Wed Jun 22 13:01:50 2011 +0100 @@ -50,6 +50,7 @@ bool isInState(QString file, State s) const; QStringList filesInState(State s) const; State stateOf(QString file) const; + bool isKnown(QString file) const; enum Activity { diff -r 498c2ca0b367 -r 2af4b5b0bf83 src/filestatuswidget.cpp --- a/src/filestatuswidget.cpp Mon Jun 13 16:56:01 2011 +0100 +++ b/src/filestatuswidget.cpp Wed Jun 22 13:01:50 2011 +0100 @@ -67,8 +67,9 @@ m_actionLabels[FileStates::Remove] = tr("Remove from version control"); m_actionLabels[FileStates::RedoMerge] = tr("Redo merge"); m_actionLabels[FileStates::MarkResolved] = tr("Mark conflict as resolved"); - m_actionLabels[FileStates::Ignore] = tr("Ignore"); - m_actionLabels[FileStates::UnIgnore] = tr("Stop ignoring"); + m_actionLabels[FileStates::Ignore] = tr("Ignore..."); + // Unignore is too difficult in fact, so we just offer to edit the hgignore + m_actionLabels[FileStates::UnIgnore] = tr("Edit .hgignore File"); m_descriptions[FileStates::Clean] = tr("You have not changed these files."); m_descriptions[FileStates::Modified] = tr("You have changed these files since you last committed them."); @@ -125,11 +126,6 @@ FileStates::Activities activities = m_fileStates.activitiesSupportedBy(s); int prevGroup = -1; foreach (FileStates::Activity a, activities) { - // Skip activities which are not yet implemented - if (a == FileStates::Ignore || - a == FileStates::UnIgnore) { - continue; - } int group = FileStates::activityGroup(a); if (group != prevGroup && prevGroup != -1) { QAction *sep = new QAction("", w); diff -r 498c2ca0b367 -r 2af4b5b0bf83 src/hgignoredialog.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hgignoredialog.cpp Wed Jun 22 13:01:50 2011 +0100 @@ -0,0 +1,155 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on hgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "hgignoredialog.h" +#include "common.h" +#include "debug.h" + +#include +#include +#include +#include +#include + +HgIgnoreDialog::HgIgnoreDialog(QWidget *parent, + QString title, + QString introText, + QString question, + QStringList options, + QString okButtonText) : + QDialog(parent) +{ + setWindowTitle(title); + + QGridLayout *layout = new QGridLayout; + setLayout(layout); + + int row = 0; + + QLabel *label = new QLabel(QString("%1%2").arg(introText).arg(question)); + label->setWordWrap(true); + layout->addWidget(label, row++, 0, 1, 2); + + if (!options.empty()) { + layout->addWidget(new QLabel(" "), row, 0); + layout->setColumnStretch(1, 10); + bool first = true; + foreach (QString option, options) { + QRadioButton *b = new QRadioButton(option); + layout->addWidget(b, row++, 1); + if (first) { + m_option = option; + b->setChecked(true); + first = false; + } + connect(b, SIGNAL(toggled(bool)), this, SLOT(optionToggled(bool))); + } + } + + QDialogButtonBox *bbox = new QDialogButtonBox(QDialogButtonBox::Ok | + QDialogButtonBox::Cancel); + layout->addWidget(bbox, row++, 0, 1, 2); + bbox->button(QDialogButtonBox::Ok)->setDefault(true); + bbox->button(QDialogButtonBox::Ok)->setText(okButtonText); + bbox->button(QDialogButtonBox::Cancel)->setAutoDefault(false); + + connect(bbox, SIGNAL(accepted()), this, SLOT(accept())); + connect(bbox, SIGNAL(rejected()), this, SLOT(reject())); +} + +void +HgIgnoreDialog::optionToggled(bool checked) +{ + QObject *s = sender(); + QRadioButton *rb = qobject_cast(s); + if (rb && checked) { + m_option = rb->text(); + } +} + +HgIgnoreDialog::IgnoreType +HgIgnoreDialog::confirmIgnore(QWidget *parent, + QStringList files, + QStringList suffixes, + QString directory) +{ + QString intro = "

"; + intro += tr("Ignore files"); + intro += "

"; + + if (files.size() < 10) { + intro += tr("You have asked to ignore the following files:

"); + intro += "   " + + files.join("
   ") + "
"; + } else { + intro += tr("You have asked to ignore %n file(s).", "", files.size()); + } + + intro += "

"; + + QString textTheseFiles; + QString textTheseNames; + if (files.size() > 1) { + textTheseFiles = tr("Ignore these files only"); + textTheseNames = tr("Ignore files with these names, in any folder"); + } else { + textTheseFiles = tr("Ignore this file only"); + textTheseNames = tr("Ignore files with the same name as this, in any folder"); + } + + QString textThisFolder; + QString textTheseSuffixes; + + QStringList options; + options << textTheseFiles; + options << textTheseNames; + + if (directory != "") { + textThisFolder = tr("Ignore the whole folder \"%1\"") + .arg(directory); + options << textThisFolder; + } + + if (suffixes.size() > 1) { + + textTheseSuffixes = tr("Ignore all files with these extensions:\n%1") + .arg(suffixes.join(", ")); + options << textTheseSuffixes; + + } else if (suffixes.size() > 0) { + + textTheseSuffixes = tr("Ignore all files with the extension \"%1\"") + .arg(suffixes[0]); + options << textTheseSuffixes; + } + + HgIgnoreDialog d(parent, tr("Ignore files"), + intro, tr("

Please choose whether to:

"), + options, tr("Ignore")); + + if (d.exec() == QDialog::Accepted) { + QString option = d.getOption(); + DEBUG << "HgIgnoreDialog::confirmIgnore: option = " << option << endl; + if (option == textTheseFiles) return IgnoreGivenFilesOnly; + else if (option == textTheseNames) return IgnoreAllFilesOfGivenNames; + else if (option == textTheseSuffixes) return IgnoreAllFilesOfGivenSuffixes; + else if (option == textThisFolder) return IgnoreWholeDirectory; + } + + return IgnoreNothing; +} + diff -r 498c2ca0b367 -r 2af4b5b0bf83 src/hgignoredialog.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hgignoredialog.h Wed Jun 22 13:01:50 2011 +0100 @@ -0,0 +1,57 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on hgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _HGIGNORE_DIALOG_H_ +#define _HGIGNORE_DIALOG_H_ + +#include + +class HgIgnoreDialog : public QDialog +{ + Q_OBJECT + +public: + enum IgnoreType { + IgnoreNothing, + IgnoreGivenFilesOnly, + IgnoreAllFilesOfGivenNames, + IgnoreAllFilesOfGivenSuffixes, + IgnoreWholeDirectory + }; + + static IgnoreType confirmIgnore(QWidget *parent, + QStringList files, + QStringList suffixes, + QString directory); + +private slots: + void optionToggled(bool); + +private: + HgIgnoreDialog(QWidget *parent, + QString title, + QString introText, + QString question, + QStringList options, + QString okButtonText); + + QString getOption() const { return m_option; } + + QString m_option; +}; + +#endif diff -r 498c2ca0b367 -r 2af4b5b0bf83 src/mainwindow.cpp --- a/src/mainwindow.cpp Mon Jun 13 16:56:01 2011 +0100 +++ b/src/mainwindow.cpp Wed Jun 22 13:01:50 2011 +0100 @@ -48,6 +48,7 @@ #include "annotatedialog.h" #include "version.h" #include "workstatuswidget.h" +#include "hgignoredialog.h" MainWindow::MainWindow(QString myDirPath) : @@ -546,15 +547,11 @@ } } -void MainWindow::hgIgnore() +void MainWindow::initHgIgnore() { - QString hgIgnorePath; - QStringList params; - - hgIgnorePath = m_workFolderPath; - hgIgnorePath += "/.hgignore"; - if (!QDir(m_workFolderPath).exists()) return; + QString hgIgnorePath = m_workFolderPath + "/.hgignore"; + QFile f(hgIgnorePath); if (!f.exists()) { f.open(QFile::WriteOnly); @@ -563,14 +560,25 @@ delete ts; f.close(); } - +} + +void MainWindow::hgEditIgnore() +{ + if (!QDir(m_workFolderPath).exists()) return; + + initHgIgnore(); + + QString hgIgnorePath = m_workFolderPath + "/.hgignore"; + QStringList params; + params << hgIgnorePath; QString editor = getEditorBinaryName(); if (editor == "") { - DEBUG << "Failed to find a text editor" << endl; - //!!! visible error! + QMessageBox::critical + (this, tr("Edit .hgignore"), + tr("Failed to locate a system text editor program!")); return; } @@ -580,16 +588,161 @@ m_runner->requestAction(action); } +static QString regexEscape(QString filename) +{ + return filename + .replace(".", "\\.") + .replace("[", "\\[") + .replace("]", "\\]") + .replace("(", "\\(") + .replace(")", "\\)") + .replace("?", "\\?"); +} + void MainWindow::hgIgnoreFiles(QStringList files) { - //!!! not implemented yet - DEBUG << "MainWindow::hgIgnoreFiles: Not implemented" << endl; + if (!QDir(m_workFolderPath).exists() || files.empty()) return; + + // we should: + // + // * show the user the list of file names selected + // + // * offer a choice (depending on the files selected?) + // + // - ignore only these files + // + // - ignore files with these names, in any subdirectories? + // + // - ignore all files with this extension (if they have a common + // extension)? + // + // - ignore all files with these extensions (if they have any + // extensions?) + + DEBUG << "MainWindow::hgIgnoreFiles: File names are:" << endl; + foreach (QString file, files) DEBUG << file << endl; + + QSet suffixes; + foreach (QString file, files) { + QString s = QFileInfo(file).suffix(); + if (s != "") suffixes.insert(s); + } + + QString directory; + bool dirCount = 0; + foreach (QString file, files) { + QString d = QFileInfo(file).path(); + if (d != directory) { + ++dirCount; + directory = d; + } + } + if (dirCount != 1 || directory == ".") directory = ""; + + HgIgnoreDialog::IgnoreType itype = + HgIgnoreDialog::confirmIgnore + (this, files, QStringList::fromSet(suffixes), directory); + + DEBUG << "hgIgnoreFiles: Ignore type is " << itype << endl; + + if (itype == HgIgnoreDialog::IgnoreNothing) return; + + // Now, .hgignore can be switched from regex to glob syntax + // part-way through -- and glob is much simpler for us, so we + // should do that if it's in regex mode at the end of the file. + + initHgIgnore(); + + QString hgIgnorePath = m_workFolderPath + "/.hgignore"; + + // hgignore file should now exist (initHgIgnore should have + // created it if it didn't). Check for glob status first + + QFile f(hgIgnorePath); + if (!f.exists()) { + std::cerr << "MainWindow::ignoreFiles: Internal error: .hgignore file not found (even though we were supposed to have created it)" << std::endl; + return; + } + + f.open(QFile::ReadOnly); + bool glob = false; + while (!f.atEnd()) { + QByteArray ba = f.readLine(); + QString s = QString::fromLocal8Bit(ba).trimmed(); + if (s.startsWith("syntax:")) { + if (s.endsWith("glob")) { + glob = true; + } else { + glob = false; + } + } + } + f.close(); + + f.open(QFile::Append); + QTextStream out(&f); + + if (!glob) { + out << "syntax: glob" << endl; + } + + QString info = "

" + tr("Ignored files") + "

"; + info += tr("The following lines have been added to the .hgignore file for this working copy:"); + info += "

"; + + QStringList args; + if (itype == HgIgnoreDialog::IgnoreAllFilesOfGivenSuffixes) { + args = QStringList::fromSet(suffixes); + } else if (itype == HgIgnoreDialog::IgnoreGivenFilesOnly) { + args = files; + } else if (itype == HgIgnoreDialog::IgnoreAllFilesOfGivenNames) { + QSet names; + foreach (QString f, files) { + names << QFileInfo(f).fileName(); + } + args = QStringList::fromSet(names); + } else if (itype == HgIgnoreDialog::IgnoreWholeDirectory) { + args << directory; + } + + bool first = true; + + foreach (QString a, args) { + QString line; + if (itype == HgIgnoreDialog::IgnoreAllFilesOfGivenSuffixes) { + line = "*." + a; + } else if (itype == HgIgnoreDialog::IgnoreGivenFilesOnly) { + // Doesn't seem to be possible to do this with a glob, + // because the glob is always unanchored and there is no + // equivalent of ^ to anchor it + line = "re:^" + regexEscape(a); + } else if (itype == HgIgnoreDialog::IgnoreAllFilesOfGivenNames) { + line = a; + } else if (itype == HgIgnoreDialog::IgnoreWholeDirectory) { + line = "re:^" + regexEscape(a) + "/"; + } + if (line != "") { + out << line << endl; + if (!first) info += "
"; + first = false; + info += xmlEncode(line); + } + } + + f.close(); + + info += "
"; + + QMessageBox::information(this, tr("Ignored files"), + info); + + hgRefresh(); } void MainWindow::hgUnIgnoreFiles(QStringList files) { - //!!! not implemented yet - DEBUG << "MainWindow::hgUnIgnoreFiles: Not implemented" << endl; + // Not implemented: edit the .hgignore instead + hgEditIgnore(); } QString MainWindow::getDiffBinaryName() @@ -2339,7 +2492,7 @@ connect(m_hgUpdateAct, SIGNAL(triggered()), this, SLOT(hgUpdate())); connect(m_hgRevertAct, SIGNAL(triggered()), this, SLOT(hgRevert())); connect(m_hgMergeAct, SIGNAL(triggered()), this, SLOT(hgMerge())); - connect(m_hgIgnoreAct, SIGNAL(triggered()), this, SLOT(hgIgnore())); + connect(m_hgEditIgnoreAct, SIGNAL(triggered()), this, SLOT(hgEditIgnore())); connect(m_settingsAct, SIGNAL(triggered()), this, SLOT(settings())); connect(m_openAct, SIGNAL(triggered()), this, SLOT(open())); @@ -2493,7 +2646,7 @@ m_hgCommitAct->setEnabled(m_localRepoActionsEnabled); m_hgMergeAct->setEnabled(m_localRepoActionsEnabled); m_hgServeAct->setEnabled(m_localRepoActionsEnabled); - m_hgIgnoreAct->setEnabled(m_localRepoActionsEnabled); + m_hgEditIgnoreAct->setEnabled(m_localRepoActionsEnabled); DEBUG << "m_localRepoActionsEnabled = " << m_localRepoActionsEnabled << endl; DEBUG << "canCommit = " << m_hgTabs->canCommit() << endl; @@ -2710,8 +2863,8 @@ //Advanced actions - m_hgIgnoreAct = new QAction(tr("Edit .hgignore File"), this); - m_hgIgnoreAct->setStatusTip(tr("Edit the .hgignore file, containing the names of files that should be ignored by Mercurial")); + m_hgEditIgnoreAct = new QAction(tr("Edit .hgignore File"), this); + m_hgEditIgnoreAct->setStatusTip(tr("Edit the .hgignore file, containing the names of files that should be ignored by Mercurial")); m_hgServeAct = new QAction(tr("Serve via HTTP"), this); m_hgServeAct->setStatusTip(tr("Serve local repository via http for workgroup access")); @@ -2760,7 +2913,7 @@ remoteMenu->addAction(m_changeRemoteRepoAct); m_advancedMenu = menuBar()->addMenu(tr("&Advanced")); - m_advancedMenu->addAction(m_hgIgnoreAct); + m_advancedMenu->addAction(m_hgEditIgnoreAct); m_advancedMenu->addSeparator(); m_advancedMenu->addAction(m_hgServeAct); diff -r 498c2ca0b367 -r 2af4b5b0bf83 src/mainwindow.h --- a/src/mainwindow.h Mon Jun 13 16:56:01 2011 +0100 +++ b/src/mainwindow.h Wed Jun 22 13:01:50 2011 +0100 @@ -93,7 +93,7 @@ void hgNewBranch(); void hgNoBranch(); void hgServe(); - void hgIgnore(); + void hgEditIgnore(); void hgAnnotateFiles(QStringList); void hgDiffFiles(QStringList); @@ -120,6 +120,8 @@ void hgLog(); void hgLogIncremental(QStringList prune); + void initHgIgnore(); + void updateRecentMenu(); void createActions(); void connectActions(); @@ -211,12 +213,13 @@ QAction *m_hgRevertAct; QAction *m_hgAddAct; QAction *m_hgRemoveAct; + QAction *m_hgIgnoreAct; QAction *m_hgUpdateAct; QAction *m_hgCommitAct; QAction *m_hgMergeAct; QAction *m_hgUpdateToRevAct; QAction *m_hgAnnotateAct; - QAction *m_hgIgnoreAct; + QAction *m_hgEditIgnoreAct; QAction *m_hgServeAct; // Menus