changeset 163:5c262ac73948

* First cut of work on merge/resolve logic
author Chris Cannam
date Fri, 03 Dec 2010 19:35:04 +0000
parents 910c2c5d1873
children de39da2f9f4d
files filestates.cpp filestates.h filestatuswidget.cpp filestatuswidget.h hgaction.h hgtabwidget.cpp hgtabwidget.h mainwindow.cpp mainwindow.h
diffstat 9 files changed, 251 insertions(+), 91 deletions(-) [+]
line wrap: on
line diff
--- a/filestates.cpp	Fri Dec 03 14:43:32 2010 +0000
+++ b/filestates.cpp	Fri Dec 03 19:35:04 2010 +0000
@@ -38,17 +38,19 @@
 FileStates::State FileStates::charToState(QChar c, bool *ok)
 {
     // Note that InConflict does not correspond to a stat char -- it's
-    // reported separately, by resolve --list -- stat reports files in
-    // conflict as M which means they will appear in more than one bin
-    // if we handle them naively.
+    // reported separately, by resolve --list, which shows U for
+    // Unresolved -- stat reports files in conflict as M, which means
+    // they will appear in more than one bin if we handle them
+    // naively.  'u' is also used by stat as the command option for
+    // Unknown, but the stat output uses ? for these so there's no
+    // ambiguity in parsing.
 
-    //!!! -- but InConflict isn't actually handled elsewhere, it's
-    //!!! -- only a placeholder really at the moment
     if (ok) *ok = true;
     if (c == 'M') return Modified;
     if (c == 'A') return Added;
     if (c == 'R') return Removed;
     if (c == '!') return Missing;
+    if (c == 'U') return InConflict;
     if (c == '?') return Unknown;
     if (c == 'C') return Clean;
     if (ok) *ok = false;
@@ -73,6 +75,7 @@
     text.replace("\r\n", "\n");
 
     clearBuckets();
+    m_stateMap.clear();
 
     QStringList lines = text.split("\n", QString::SkipEmptyParts);
 
@@ -88,9 +91,13 @@
         if (!ok) continue;
 
         QString file = line.right(line.length() - 2);
-        QStringList *bucket = stateToBucket(s);
+        m_stateMap[file] = s;
+    }
+
+    foreach (QString file, m_stateMap.keys()) {
+
+        QStringList *bucket = stateToBucket(m_stateMap[file]);
         bucket->push_back(file);
-        m_stateMap[file] = s;
     }
 
     DEBUG << "FileStates: " << m_modified.size() << " modified, " << m_added.size()
--- a/filestates.h	Fri Dec 03 14:43:32 2010 +0000
+++ b/filestates.h	Fri Dec 03 19:35:04 2010 +0000
@@ -29,16 +29,19 @@
 
     enum State {
 
-        Clean,
+        // These are in the order in which they want to be listed in
+        // the interface
+
         Modified,
         Added,
+        Removed,
+        InConflict,
+        Missing,
+        Clean,
         Unknown,
-        Removed,
-        Missing,
-        InConflict,
 
-        FirstState = Clean,
-        LastState = InConflict
+        FirstState = Modified,
+        LastState = Unknown
     };
 
     void parseStates(QString text);
--- a/filestatuswidget.cpp	Fri Dec 03 14:43:32 2010 +0000
+++ b/filestatuswidget.cpp	Fri Dec 03 19:35:04 2010 +0000
@@ -70,6 +70,7 @@
     m_simpleLabels[FileStates::Added] = tr("Added:");
     m_simpleLabels[FileStates::Removed] = tr("Removed:");
     m_simpleLabels[FileStates::Missing] = tr("Missing:");
+    m_simpleLabels[FileStates::InConflict] = tr("In Conflict:");
     m_simpleLabels[FileStates::Unknown] = tr("Untracked:");
 
     m_descriptions[FileStates::Clean] = tr("You have not changed these files.");
@@ -80,6 +81,7 @@
     m_descriptions[FileStates::Missing] = tr("These files are recorded in the version control, but absent from your working folder.<br>"
                                              "If you intended to delete them, select them and use Remove to tell the version control system about it.<br>"
                                              "If you deleted them by accident, select them and use Revert to restore their previous contents.");
+    m_descriptions[FileStates::InConflict] = tr("These files are unresolved following an incomplete merge.<br>Select a file and use Merge to try to resolve the merge again.");
     m_descriptions[FileStates::Unknown] = tr("These files are in your working folder but are not under version control.<br>"
                                              "Select a file and use Add to place it under version control or Ignore to remove it from this list.");
 
@@ -87,7 +89,7 @@
                                 "have appeared since your most recent commit or update.");
 
     for (int i = int(FileStates::FirstState);
-             i <= int(FileStates::LastState); ++i) {
+         i <= int(FileStates::LastState); ++i) {
 
         FileStates::State s = FileStates::State(i);
 
@@ -222,6 +224,7 @@
         case FileStates::Modified:
         case FileStates::Removed:
         case FileStates::Missing:
+        case FileStates::InConflict:
             files.push_back(f);
             break;
         default: break;
@@ -237,6 +240,28 @@
     files << m_fileStates.getFilesInState(FileStates::Added);
     files << m_fileStates.getFilesInState(FileStates::Removed);
     files << m_fileStates.getFilesInState(FileStates::Missing);
+    files << m_fileStates.getFilesInState(FileStates::InConflict);
+    return files;
+}
+
+QStringList FileStatusWidget::getSelectedUnresolvedFiles() const
+{
+    QStringList files;
+    foreach (QString f, m_selectedFiles) {
+        switch (m_fileStates.getStateOfFile(f)) {
+        case FileStates::InConflict:
+            files.push_back(f);
+            break;
+        default: break;
+        }
+    }
+    return files;
+}
+
+QStringList FileStatusWidget::getAllUnresolvedFiles() const
+{
+    QStringList files;
+    files << m_fileStates.getFilesInState(FileStates::InConflict);
     return files;
 }
 
@@ -272,6 +297,7 @@
         case FileStates::Added:
         case FileStates::Modified:
         case FileStates::Missing:
+        case FileStates::InConflict:
             files.push_back(f);
             break;
         default: break;
@@ -287,6 +313,7 @@
     files << m_fileStates.getFilesInState(FileStates::Added);
     files << m_fileStates.getFilesInState(FileStates::Modified);
     files << m_fileStates.getFilesInState(FileStates::Missing);
+    files << m_fileStates.getFilesInState(FileStates::InConflict);
     return files;
 }
 
--- a/filestatuswidget.h	Fri Dec 03 14:43:32 2010 +0000
+++ b/filestatuswidget.h	Fri Dec 03 19:35:04 2010 +0000
@@ -64,6 +64,9 @@
     QStringList getSelectedRemovableFiles() const;
     QStringList getAllRemovableFiles() const;
 
+    QStringList getSelectedUnresolvedFiles() const;
+    QStringList getAllUnresolvedFiles() const;
+    
 signals:
     void selectionChanged();
 
--- a/hgaction.h	Fri Dec 03 14:43:32 2010 +0000
+++ b/hgaction.h	Fri Dec 03 19:35:04 2010 +0000
@@ -27,6 +27,7 @@
     ACT_QUERY_PATHS,
     ACT_QUERY_BRANCH,
     ACT_STAT,
+    ACT_RESOLVE_LIST,
     ACT_QUERY_HEADS,
     ACT_QUERY_PARENTS,
     ACT_LOG,
@@ -45,7 +46,6 @@
     ACT_UPDATE,
     ACT_REVERT,
     ACT_MERGE,
-    ACT_RESOLVE_LIST,
     ACT_SERVE,
     ACT_RESOLVE_MARK,
     ACT_RETRY_MERGE,
--- a/hgtabwidget.cpp	Fri Dec 03 14:43:32 2010 +0000
+++ b/hgtabwidget.cpp	Fri Dec 03 19:35:04 2010 +0000
@@ -78,7 +78,9 @@
 
 void HgTabWidget::setCurrent(QStringList ids, QString branch)
 {
-    m_historyWidget->setCurrent(ids, branch, canCommit());
+    bool showUncommitted = false;
+    if (canRevert()) showUncommitted = true;
+    m_historyWidget->setCurrent(ids, branch, showUncommitted);
 }
 
 void HgTabWidget::updateHistory()
@@ -86,16 +88,25 @@
     m_historyWidget->update();
 }
 
+bool HgTabWidget::canDiff() const
+{
+    if (!m_fileStatusWidget->getSelectedAddableFiles().empty()) return false;
+    return m_fileStatusWidget->haveChangesToCommit() ||
+        !m_fileStatusWidget->getAllUnresolvedFiles().empty();
+}
+
 bool HgTabWidget::canCommit() const
 {
     if (!m_fileStatusWidget->getSelectedAddableFiles().empty()) return false;
-    return m_fileStatusWidget->haveChangesToCommit();
+    return m_fileStatusWidget->haveChangesToCommit() &&
+        m_fileStatusWidget->getAllUnresolvedFiles().empty();
 }
 
 bool HgTabWidget::canRevert() const
 {
+    if (!m_fileStatusWidget->getSelectedAddableFiles().empty()) return false;
     return m_fileStatusWidget->haveChangesToCommit() ||
-        !m_fileStatusWidget->getSelectedRevertableFiles().empty();
+        !m_fileStatusWidget->getAllUnresolvedFiles().empty();
 }
 
 bool HgTabWidget::canAdd() const
@@ -113,9 +124,9 @@
     return true;
 }
 
-bool HgTabWidget::canDoDiff() const
+bool HgTabWidget::canResolve() const
 {
-    return canCommit();
+    return !m_fileStatusWidget->getSelectedUnresolvedFiles().empty();
 }
 
 QStringList HgTabWidget::getAllSelectedFiles() const
@@ -158,6 +169,16 @@
     return m_fileStatusWidget->getSelectedRemovableFiles();
 }
 
+QStringList HgTabWidget::getAllUnresolvedFiles() const
+{
+    return m_fileStatusWidget->getAllUnresolvedFiles();
+}
+
+QStringList HgTabWidget::getSelectedUnresolvedFiles() const
+{
+    return m_fileStatusWidget->getSelectedUnresolvedFiles();
+}
+
 void HgTabWidget::updateWorkFolderFileList(QString fileList)
 {
     m_fileStates.parseStates(fileList);
--- a/hgtabwidget.h	Fri Dec 03 14:43:32 2010 +0000
+++ b/hgtabwidget.h	Fri Dec 03 19:35:04 2010 +0000
@@ -54,11 +54,12 @@
 
     FileStates getFileStates() { return m_fileStates; }
 
+    bool canDiff() const;
     bool canCommit() const;
     bool canRevert() const;
     bool canAdd() const;
     bool canRemove() const;
-    bool canDoDiff() const;
+    bool canResolve() const;
 
     QStringList getAllSelectedFiles() const;
 
@@ -74,6 +75,9 @@
     QStringList getSelectedRemovableFiles() const;
     QStringList getAllRemovableFiles() const;
 
+    QStringList getSelectedUnresolvedFiles() const;
+    QStringList getAllUnresolvedFiles() const;
+
 signals:
     void selectionChanged();
 
--- a/mainwindow.cpp	Fri Dec 03 14:43:32 2010 +0000
+++ b/mainwindow.cpp	Fri Dec 03 19:35:04 2010 +0000
@@ -47,6 +47,7 @@
 
     fsWatcher = 0;
     commitsSincePush = 0;
+    shouldHgStat = true;
 
     createActions();
     createMenus();
@@ -85,6 +86,7 @@
     }
 
     findDiffBinaryName();
+    findMergeBinaryName();
 
     ColourSet *cs = ColourSet::instance();
     cs->clearDefaultNames();
@@ -161,6 +163,8 @@
     QStringList params;
     params << "stat" << "-ardum";
 
+    lastStatOutput = "";
+
     // annoyingly, hg stat actually modifies the working directory --
     // it creates files called hg-checklink and hg-checkexec to test
     // properties of the filesystem
@@ -240,19 +244,6 @@
     }
 }
 
-void MainWindow::hgResolveMark()
-{
-    QStringList params;
-    QString currentFile;//!!! = hgTabs -> getCurrentFileListLine();
-
-    if (!currentFile.isEmpty())
-    {
-        params << "resolve" << "--mark" << "--" << currentFile.mid(2);   //Jump over status marker characters (e.g "M ")
-
-        runner->requestAction(HgAction(ACT_RESOLVE_MARK, workFolderPath, params));
-    }
-}
-
 void MainWindow::hgResolveList()
 {
     QStringList params;
@@ -331,8 +322,6 @@
         
         runner->requestAction(HgAction(ACT_COMMIT, workFolderPath, params));
     }
-    
-    justMerged = false;
 }
 
 QString MainWindow::filterTag(QString tag)
@@ -424,6 +413,30 @@
     diffBinaryName = diff;
 }
 
+void MainWindow::findMergeBinaryName()
+{
+    QSettings settings;
+    QString merge = settings.value("mergebinary", "").toString();
+    if (merge == "") {
+        QStringList bases;
+        bases << "fmdiff3" << "kdiff3" << "meld" << "diffuse";
+        bool found = false;
+        foreach (QString base, bases) {
+            merge = findExecutable(base);
+            if (merge != base) {
+                found = true;
+                break;
+            }
+        }
+        if (found) {
+            settings.setValue("mergebinary", merge);
+        } else {
+            merge = "";
+        }
+    }
+    mergeBinaryName = merge;
+}
+
 void MainWindow::hgFolderDiff()
 {
     if (diffBinaryName == "") return;
@@ -443,6 +456,8 @@
 
 void MainWindow::hgDiffToCurrent(QString id)
 {
+    if (diffBinaryName == "") return;
+
     QStringList params;
 
     // Diff given revision against working folder
@@ -457,6 +472,8 @@
 
 void MainWindow::hgDiffToParent(QString child, QString parent)
 {
+    if (diffBinaryName == "") return;
+
     QStringList params;
 
     // Diff given revision against working folder
@@ -508,32 +525,92 @@
          tr("<h3>%1</h3><p>%2").arg(rf)
          .arg(tr("You are about to <b>revert</b> %n file(s).<br><br>This will <b>throw away any changes</b> that you have made to these files but have not committed.", "", files.size())),
          files)) {
+
+        lastRevertedFiles = files;
         
         if (files.empty()) {
             params << "revert" << "--no-backup";
         } else {
             params << "revert" << "--no-backup" << "--" << files;
         }
+
+        //!!! This is problematic.  If you've got an uncommitted
+        //!!! merge, you can't revert it without declaring which
+        //!!! parent of the merge you want to revert to (reasonably
+        //!!! enough).  We're OK if we just did the merge in easyhg a
+        //!!! moment ago, because we have a record of which parent was
+        //!!! the target -- but if you exit and restart, we've lost
+        //!!! that record and it doesn't appear to be possible to get
+        //!!! it back from Hg.  Even if you just switched from one
+        //!!! repo to another, the record is lost.  What to do?
+
+        if (justMerged && mergeTargetRevision != "") {
+            params << "--rev" << mergeTargetRevision;
+        }
         
         runner->requestAction(HgAction(ACT_REVERT, workFolderPath, params));
     }
 }
 
+
+void MainWindow::hgMarkResolved(QStringList files)
+{
+    QStringList params;
+
+    params << "resolve" << "--mark";
+
+    if (files.empty()) {
+        params << "--all";
+    } else {
+        params << files;
+    }
+
+    runner->requestAction(HgAction(ACT_RESOLVE_MARK, workFolderPath, params));
+}
+
+
 void MainWindow::hgRetryMerge()
 {
     QStringList params;
 
-    params << "resolve" << "--all";
+    params << "resolve";
+
+    if (mergeBinaryName != "") {
+        params << "--tool" << mergeBinaryName;
+    }
+
+    QStringList files = hgTabs->getSelectedUnresolvedFiles();
+    if (files.empty()) {
+        params << "--all";
+    } else {
+        params << files;
+    }
+
     runner->requestAction(HgAction(ACT_RETRY_MERGE, workFolderPath, params));
+
+    mergeCommitComment = tr("Merge");
 }
 
 
 void MainWindow::hgMerge()
 {
+    if (hgTabs->canResolve()) {
+        hgRetryMerge();
+        return;
+    }
+
     QStringList params;
 
     params << "merge";
-    
+
+    if (mergeBinaryName != "") {
+        params << "--tool" << mergeBinaryName;
+    }
+
+    if (currentParents.size() == 1) {
+        mergeTargetRevision = currentParents[0]->id();
+    }
+
     runner->requestAction(HgAction(ACT_MERGE, workFolderPath, params));
 
     mergeCommitComment = tr("Merge");
@@ -546,6 +623,10 @@
 
     params << "merge";
     params << "--rev" << Changeset::hashOf(id);
+
+    if (mergeBinaryName != "") {
+        params << "--tool" << mergeBinaryName;
+    }
     
     runner->requestAction(HgAction(ACT_MERGE, workFolderPath, params));
 
@@ -673,6 +754,10 @@
     foreach (Changeset *cs, currentHeads) delete cs;
     currentHeads.clear();
     currentBranch = "";
+    lastStatOutput = "";
+    lastRevertedFiles.clear();
+    mergeTargetRevision = "";
+    mergeCommitComment = "";
     needNewLog = true;
 }
 
@@ -1198,25 +1283,6 @@
              : "");
 
     QMessageBox::warning(this, tr("Command failed"), message);
-
-/* todo:
-if ((runningAction == ACT_MERGE) && (exitCode != 0))
-            {
-                // If we had a failed merge, offer to retry
-                if (QMessageBox::Ok == QMessageBox::information(this, tr("Retry merge ?"), tr("Merge attempt failed. retry ?"), QMessageBox::Ok | QMessageBox::Cancel))
-                {
-                    runningAction = ACT_NONE;
-                    hgRetryMerge();
-                }
-                else
-                {
-                    runningAction = ACT_NONE;
-                    hgStat();
-                }
-            }
-            else
-            {
-*/
 }
 
 void MainWindow::commandCompleted(HgAction completedAction, QString output)
@@ -1225,7 +1291,6 @@
 
     if (action == ACT_NONE) return;
 
-    bool shouldHgStat = false;
     bool headsChanged = false;
     QStringList oldHeadIds;
 
@@ -1253,17 +1318,35 @@
 
     case ACT_STAT:
         if (fsWatcher) fsWatcher->blockSignals(false);
-        hgTabs->updateWorkFolderFileList(output);
+        lastStatOutput = output;
         updateFileSystemWatcher();
         break;
+
+    case ACT_RESOLVE_LIST:
+        if (output != "") {
+            // Remove lines beginning with R (they are resolved,
+            // and the file stat parser treats R as removed)
+            QStringList outList = output.split('\n');
+            QStringList winnowed;
+            foreach (QString line, outList) {
+                if (!line.startsWith("R ")) winnowed.push_back(line);
+            }
+            output = winnowed.join("\n");
+        }
+        DEBUG << "lastStatOutput = " << lastStatOutput << endl;
+        DEBUG << "output = " << output << endl;
+        hgTabs->updateWorkFolderFileList(lastStatOutput + output);
+        break;
+
+    case ACT_RESOLVE_MARK:
+        shouldHgStat = true;
+        break;
         
     case ACT_INCOMING:
         showIncoming(output);
         break;
 
     case ACT_ANNOTATE:
-    case ACT_RESOLVE_LIST:
-    case ACT_RESOLVE_MARK:
         presentLongStdoutToUser(output);
         shouldHgStat = true;
         break;
@@ -1327,12 +1410,17 @@
 
     case ACT_COMMIT:
         hgTabs->clearSelections();
+        justMerged = false;
         shouldHgStat = true;
         break;
+
+    case ACT_REVERT:
+        hgMarkResolved(lastRevertedFiles);
+        justMerged = false;
+        break;
         
     case ACT_REMOVE:
     case ACT_ADD:
-    case ACT_REVERT:
         hgTabs->clearSelections();
         shouldHgStat = true;
         break;
@@ -1357,8 +1445,8 @@
         break;
         
     case ACT_RETRY_MERGE:
-        QMessageBox::information(this, tr("Merge retry"),
-                                 tr("Merge retry successful."));
+        QMessageBox::information(this, tr("Resolved"),
+                                 tr("<qt><h3>Merge resolved</h3><p>Merge resolved successfully.</p>"));
         shouldHgStat = true;
         justMerged = true;
         break;
@@ -1368,11 +1456,11 @@
     }
 
     // Sequence when no full log required:
-    //   paths -> branch -> stat -> heads ->
+    //   paths -> branch -> stat -> resolve-list -> heads ->
     //     incremental-log (only if heads changed) -> parents
     // 
     // Sequence when full log required:
-    //   paths -> branch -> stat -> heads -> parents -> log
+    //   paths -> branch -> stat -> resolve-list -> heads -> parents -> log
     //
     // Note we want to call enableDisableActions only once, at the end
     // of whichever sequence is in use.
@@ -1390,6 +1478,10 @@
         break;
         
     case ACT_STAT:
+        hgResolveList();
+        break;
+
+    case ACT_RESOLVE_LIST:
         hgQueryHeads();
         break;
 
@@ -1420,6 +1512,7 @@
 
     default:
         if (shouldHgStat) {
+            shouldHgStat = false;
             hgQueryPaths();
         } else {
             noMore = true;
@@ -1448,7 +1541,6 @@
     connect(hgUpdateAct, SIGNAL(triggered()), this, SLOT(hgUpdate()));
     connect(hgRevertAct, SIGNAL(triggered()), this, SLOT(hgRevert()));
     connect(hgMergeAct, SIGNAL(triggered()), this, SLOT(hgMerge()));
-    connect(hgRetryMergeAct, SIGNAL(triggered()), this, SLOT(hgRetryMerge()));
     connect(hgTagAct, SIGNAL(triggered()), this, SLOT(hgTag()));
     connect(hgIgnoreAct, SIGNAL(triggered()), this, SLOT(hgIgnore()));
 
@@ -1465,8 +1557,6 @@
 
 //    connect(hgUpdateToRevAct, SIGNAL(triggered()), this, SLOT(hgUpdateToRev()));
     connect(hgAnnotateAct, SIGNAL(triggered()), this, SLOT(hgAnnotate()));
-    connect(hgResolveListAct, SIGNAL(triggered()), this, SLOT(hgResolveList()));
-    connect(hgResolveMarkAct, SIGNAL(triggered()), this, SLOT(hgResolveMark()));
     connect(hgServeAct, SIGNAL(triggered()), this, SLOT(hgServe()));
     connect(clearSelectionsAct, SIGNAL(triggered()), this, SLOT(clearSelections()));
 }
@@ -1562,9 +1652,6 @@
     hgUpdateAct -> setEnabled(localRepoActionsEnabled);
     hgCommitAct -> setEnabled(localRepoActionsEnabled);
     hgMergeAct -> setEnabled(localRepoActionsEnabled);
-    hgRetryMergeAct -> setEnabled(localRepoActionsEnabled);
-    hgResolveListAct -> setEnabled(localRepoActionsEnabled);
-    hgResolveMarkAct -> setEnabled(localRepoActionsEnabled);
     hgAnnotateAct -> setEnabled(localRepoActionsEnabled);
     hgServeAct -> setEnabled(localRepoActionsEnabled);
     hgTagAct -> setEnabled(localRepoActionsEnabled);
@@ -1579,8 +1666,8 @@
     hgAddAct->setEnabled(localRepoActionsEnabled && hgTabs->canAdd());
     hgRemoveAct->setEnabled(localRepoActionsEnabled && hgTabs->canRemove());
     hgCommitAct->setEnabled(localRepoActionsEnabled && hgTabs->canCommit());
-    hgRevertAct->setEnabled(localRepoActionsEnabled && hgTabs->canCommit());
-    hgFolderDiffAct->setEnabled(localRepoActionsEnabled && hgTabs->canDoDiff());
+    hgRevertAct->setEnabled(localRepoActionsEnabled && hgTabs->canRevert());
+    hgFolderDiffAct->setEnabled(localRepoActionsEnabled && hgTabs->canDiff());
 
     // A default merge makes sense if:
     //  * there is only one parent (if there are two, we have an uncommitted merge) and
@@ -1624,10 +1711,13 @@
         emptyRepo = true;
     } else {
         haveMerge = true;
+        justMerged = true;
     }
         
-    hgMergeAct->setEnabled(localRepoActionsEnabled && canMerge);
-    hgUpdateAct->setEnabled(localRepoActionsEnabled && canUpdate);
+    hgMergeAct->setEnabled(localRepoActionsEnabled &&
+                           (canMerge || hgTabs->canResolve()));
+    hgUpdateAct->setEnabled(localRepoActionsEnabled &&
+                            (canUpdate && !hgTabs->canRevert()));
 
     // Set the state field on the file status widget
 
@@ -1644,10 +1734,18 @@
         hgTabs->setState(tr("Nothing committed to this repository yet"));
     } else if (canMerge) {
         hgTabs->setState(tr("<b>Awaiting merge</b> on %1").arg(branchText));
+    } else if (!hgTabs->getAllUnresolvedFiles().empty()) {
+        hgTabs->setState(tr("Have unresolved files following merge on %1").arg(branchText));
     } else if (haveMerge) {
         hgTabs->setState(tr("Have merged but not yet committed on %1").arg(branchText));
     } else if (canUpdate) {
-        hgTabs->setState(tr("On %1. Not at the head of the branch: consider updating").arg(branchText));
+        if (hgTabs->canRevert()) {
+            // have uncommitted changes
+            hgTabs->setState(tr("On %1. Not at the head of the branch").arg(branchText));
+        } else {
+            // no uncommitted changes
+            hgTabs->setState(tr("On %1. Not at the head of the branch: consider updating").arg(branchText));
+        }
     } else if (currentBranchHeads > 1) {
         hgTabs->setState(tr("At one of %n heads of %1", "", currentBranchHeads).arg(branchText));
     } else {
@@ -1722,15 +1820,6 @@
     hgAnnotateAct = new QAction(tr("Annotate"), this);
     hgAnnotateAct -> setStatusTip(tr("Show line-by-line version information for selected file"));
 
-    hgResolveListAct = new QAction(tr("Resolve (list)"), this);
-    hgResolveListAct -> setStatusTip(tr("Resolve (list): Show list of files needing merge"));
-
-    hgResolveMarkAct = new QAction(tr("Resolve (mark)"), this);
-    hgResolveMarkAct -> setStatusTip(tr("Resolve (mark): Mark selected file status as resolved"));
-
-    hgRetryMergeAct = new QAction(tr("Retry merge"), this);
-    hgRetryMergeAct -> setStatusTip(tr("Retry merge after failed merge attempt."));
-
     hgTagAct = new QAction(tr("Tag revision"), this);
     hgTagAct -> setStatusTip(tr("Give decsriptive name (tag) to current workfolder parent revision."));
 
--- a/mainwindow.h	Fri Dec 03 14:43:32 2010 +0000
+++ b/mainwindow.h	Fri Dec 03 19:35:04 2010 +0000
@@ -80,6 +80,7 @@
     void hgUpdate();
     void hgRevert();
     void hgMerge();
+    void hgMarkResolved(QStringList);
     void hgRetryMerge();
     void hgCloneFromRemote();
     void hgInit();
@@ -90,7 +91,6 @@
     void hgMergeFrom(QString);
     void hgAnnotate();
     void hgResolveList();
-    void hgResolveMark();
     void hgTag();
     void hgServe();
     void hgIgnore();
@@ -172,11 +172,8 @@
     QAction *hgUpdateAct;
     QAction *hgCommitAct;
     QAction *hgMergeAct;
-    QAction *hgRetryMergeAct;
     QAction *hgUpdateToRevAct;
     QAction *hgAnnotateAct;
-    QAction *hgResolveListAct;
-    QAction *hgResolveMarkAct;
     QAction *hgTagAct;
     QAction *hgIgnoreAct;
     QAction *hgServeAct;
@@ -199,12 +196,21 @@
 
     HgRunner *runner;
 
+    bool shouldHgStat;
+
+    QString diffBinaryName;
+    QString mergeBinaryName;
+
     void findDiffBinaryName();
-    QString diffBinaryName;
+    void findMergeBinaryName();
 
     QFileSystemWatcher *fsWatcher;
 
+    QString lastStatOutput;
+    QStringList lastRevertedFiles;
+
     bool justMerged;
+    QString mergeTargetRevision;
     QString mergeCommitComment;
 };