changeset 422:2af4b5b0bf83

Merge from branch "ignore"
author Chris Cannam <chris.cannam@eecs.qmul.ac.uk>
date Wed, 22 Jun 2011 13:01:50 +0100
parents 498c2ca0b367 (current diff) 653e9694a694 (diff)
children 06e5c4f3dd7c
files
diffstat 11 files changed, 425 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- 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
--- 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
--- 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 <QVariant>
 
@@ -53,6 +54,8 @@
 
     description = "<qt><table border=0>";
 
+//    DEBUG << "comment is " << comment() << endl;
+
     QString c = comment().trimmed();
     c = c.replace(QRegExp("^\""), "");
     c = c.replace(QRegExp("\"$"), "");
--- 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 <QMessageBox>
 #include <QInputDialog>
@@ -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)
--- 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;
--- 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 {
 
--- 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);
--- /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 <QGridLayout>
+#include <QRadioButton>
+#include <QLabel>
+#include <QDialogButtonBox>
+#include <QPushButton>
+
+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<QRadioButton *>(s);
+    if (rb && checked) {
+	m_option = rb->text();
+    }
+}
+
+HgIgnoreDialog::IgnoreType
+HgIgnoreDialog::confirmIgnore(QWidget *parent,
+			      QStringList files,
+                              QStringList suffixes,
+                              QString directory)
+{
+    QString intro = "<qt><h3>";
+    intro += tr("Ignore files");
+    intro += "</h3><p>";
+
+    if (files.size() < 10) {
+        intro += tr("You have asked to ignore the following files:</p><p>");
+        intro += "<code>&nbsp;&nbsp;&nbsp;"
+	    + files.join("<br>&nbsp;&nbsp;&nbsp;") + "</code>";
+    } else {
+        intro += tr("You have asked to ignore %n file(s).", "", files.size());
+    }
+
+    intro += "</p></qt>";
+
+    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("<p>Please choose whether to:</p>"),
+                     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;
+}
+
--- /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 <QDialog>
+
+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
--- 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<QString> 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 = "<qt><h3>" + tr("Ignored files") + "</h3><p>";
+    info += tr("The following lines have been added to the .hgignore file for this working copy:");
+    info += "</p><code>";
+
+    QStringList args;
+    if (itype == HgIgnoreDialog::IgnoreAllFilesOfGivenSuffixes) {
+        args = QStringList::fromSet(suffixes);
+    } else if (itype == HgIgnoreDialog::IgnoreGivenFilesOnly) {
+        args = files;
+    } else if (itype == HgIgnoreDialog::IgnoreAllFilesOfGivenNames) {
+        QSet<QString> 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 += "<br>";
+            first = false;
+            info += xmlEncode(line);
+        }
+    }
+
+    f.close();
+    
+    info += "</code></qt>";
+
+    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);
 
--- 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