# # # add_file "res/forms/unaccounted_renames.ui" # content [4f22b29be16ab911fd67766288f0385ffed6e9f1] # # add_file "src/view/dialogs/UnaccountedRenames.cpp" # content [938d5eac2b9884b18445e4eacde26c5d31dea0e8] # # add_file "src/view/dialogs/UnaccountedRenames.h" # content [0691bf54c8ff2d8606e88eb855613a4977ad8b9d] # # patch "guitone.pro" # from [03f06140d2a1fe2ab82feb90a374d8546ab96ab6] # to [8a3fa0c8ffda3b50a30fe54d2429a62f130b0f9c] # # patch "res/forms/main_window.ui" # from [9f9f41d6a2d521f10475aabdd7e2901b29243dd1] # to [600472d44fd5cd51100da93909a592d1815e3505] # # patch "src/model/Inventory.cpp" # from [1e554a233746a80cf81f5bac48efd24eeca7b873] # to [3cceb0b22340f61c7beba6c6d2e05392d1839cc2] # # patch "src/model/Inventory.h" # from [cb7bd39962c8f983a04c7410318e1643fb048ede] # to [c9834d77c69b3b9d7c6dc35a5a8bc6118a5b01ec] # # patch "src/monotone/Monotone.cpp" # from [0fe70345373d6aa6338a8a237f7d0ddda2a2d308] # to [e064e50d6f2e22d1c15cd325346311a4ab93e97c] # # patch "src/monotone/MonotoneDelegate.cpp" # from [6fe7d984293395f5d83ffdcf0c6735d93186a6f7] # to [0b1b0ebfaa468cbf89d3ef32a54e9791405b86f5] # # patch "src/monotone/MonotoneDelegate.h" # from [4a188eda03b4f743f508cdd05a333102dfeb0337] # to [6971e5c0d79d50fc766f84fb395cf648da80c8e5] # # patch "src/view/MainWindow.cpp" # from [a12fb6d3645a244dadb84a4e44c95bf2f203dae6] # to [7064dcc81b11c6991521f64da0762beb9a347350] # # patch "src/view/MainWindow.h" # from [38296ee6b6bbd5e84836949b27167abccb20bb6a] # to [ed423769deea41872ede542b5920a765f790a38a] # # patch "src/view/dialogs/FileHistory.cpp" # from [fb8b038667a771cbaee11c0f60d1269104e774d3] # to [47ab8b46f70a4e81ecadd0c686f00dc8874c3fde] # # patch "src/vocab.h" # from [65c921759f5565377c8ffe49fb8abbfbb43fe27c] # to [ed37b5f6deab84d3025ff1bfb2b0341993391751] # ============================================================ --- res/forms/unaccounted_renames.ui 4f22b29be16ab911fd67766288f0385ffed6e9f1 +++ res/forms/unaccounted_renames.ui 4f22b29be16ab911fd67766288f0385ffed6e9f1 @@ -0,0 +1,98 @@ + + UnaccountedRenamesDialog + + + + 0 + 0 + 454 + 316 + + + + Unaccounted renames + + + :/icons/guitone.png + + + + 9 + + + 6 + + + + + 0 + + + 6 + + + + + + + + 0 + + + 6 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Perform checked renames + + + + + + + Close + + + + + + + + + + + + + + + closeButton + clicked() + UnaccountedRenamesDialog + accept() + + + 399 + 272 + + + 252 + 154 + + + + + ============================================================ --- src/view/dialogs/UnaccountedRenames.cpp 938d5eac2b9884b18445e4eacde26c5d31dea0e8 +++ src/view/dialogs/UnaccountedRenames.cpp 938d5eac2b9884b18445e4eacde26c5d31dea0e8 @@ -0,0 +1,104 @@ +/*************************************************************************** + * Copyright (C) 2007 by Thomas Keller * + * address@hidden * + * * + * 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. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "UnaccountedRenames.h" +#include "IconProvider.h" + +#include +#include + +UnaccountedRenames::UnaccountedRenames(QWidget * parent, const QMap & r): + Dialog(parent), renames(r) +{ + setupUi(this); + Dialog::init(); + + // OSX sheet-alike dialog + setWindowFlags(Qt::Sheet); + + connect( + renameTree, SIGNAL(itemClicked(QTreeWidgetItem *, int)), + this, SLOT(itemClicked(QTreeWidgetItem *, int)) + ); + + renameTree->setHeaderLabels(QStringList() + << tr("Missing paths and possible rename targets") + << tr("File ID (if applicable)") + ); + + renameTree->header()->resizeSection(0, 300); + + QFont boldFont; + boldFont.setBold(true); + IconProvider * iconProvider = IconProvider::singleton(); + + QMapIterator i(renames); + while (i.hasNext()) + { + i.next(); + FileEntry sen = i.key(); + FileEntryList list = i.value(); + QTreeWidgetItem * source = new QTreeWidgetItem(renameTree); + source->setFlags(Qt::ItemIsEnabled); + source->setText(0, QString("%1 (%2)").arg(sen.path).arg(list.size())); + source->setData(0, Qt::UserRole, QVariant(sen.path)); + source->setFont(0, boldFont); + source->setIcon(0, sen.is_dir ? + iconProvider->getPlainFolderIcon() : + iconProvider->getPlainFileIcon()); + source->setText(1, sen.fileid.isEmpty() ? tr("n/a") : sen.fileid); + source->setFont(1, boldFont); + + foreach (FileEntry en, list) + { + QTreeWidgetItem * target = new QTreeWidgetItem(source); + target->setFlags(Qt::ItemIsEnabled | Qt::ItemIsUserCheckable); + target->setCheckState(0, Qt::Unchecked); + target->setText(0, en.path); + target->setToolTip(0, en.fileid.isEmpty() || sen.fileid != en.fileid ? + tr("matched by name") : + tr("matched by content")); + + target->setText(1, en.fileid.isEmpty() ? tr("n/a") : en.fileid); + } + } +} + +UnaccountedRenames::~UnaccountedRenames() +{ +} + +void UnaccountedRenames::itemClicked(QTreeWidgetItem * item, int column) +{ + if (item == 0 || column == -1) return; + QTreeWidgetItem * parent = item->parent(); + // top level item clicked + if (!parent) return; + + Qt::CheckState state = item->checkState(0); + + for (int i=0, j=parent->childCount(); ichild(i)->setCheckState(0, Qt::Unchecked); + } + + item->setCheckState(0, state); +} + ============================================================ --- src/view/dialogs/UnaccountedRenames.h 0691bf54c8ff2d8606e88eb855613a4977ad8b9d +++ src/view/dialogs/UnaccountedRenames.h 0691bf54c8ff2d8606e88eb855613a4977ad8b9d @@ -0,0 +1,43 @@ +/*************************************************************************** + * Copyright (C) 2007 by Thomas Keller * + * address@hidden * + * * + * 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. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef UNACCOUNTED_RENAMES_H +#define UNACCOUNTED_RENAMES_H + +#include "Dialog.h" +#include "vocab.h" +#include "ui_unaccounted_renames.h" + +class UnaccountedRenames : public Dialog, private Ui::UnaccountedRenamesDialog +{ + Q_OBJECT + +public: + UnaccountedRenames(QWidget *, const QMap &); + ~UnaccountedRenames(); + +private: + QMap renames; + +private slots: + void itemClicked(QTreeWidgetItem *, int); +}; + +#endif ============================================================ --- guitone.pro 03f06140d2a1fe2ab82feb90a374d8546ab96ab6 +++ guitone.pro 8a3fa0c8ffda3b50a30fe54d2429a62f130b0f9c @@ -36,6 +36,7 @@ HEADERS = src/view/MainWindow.h \ src/view/dialogs/RevisionManifest.h \ src/view/dialogs/CommitRevision.h \ src/view/dialogs/FileHistory.h \ + src/view/dialogs/UnaccountedRenames.h \ src/monotone/Monotone.h \ src/monotone/MonotoneDelegate.h \ src/monotone/FileExporter.h \ @@ -96,6 +97,7 @@ SOURCES += src/view/MainWindow.cpp \ src/view/dialogs/RevisionManifest.cpp \ src/view/dialogs/CommitRevision.cpp \ src/view/dialogs/FileHistory.cpp \ + src/view/dialogs/UnaccountedRenames.cpp \ src/monotone/Monotone.cpp \ src/monotone/MonotoneDelegate.cpp \ src/monotone/FileExporter.cpp \ @@ -149,7 +151,8 @@ FORMS += res/forms/select_revision.ui res/forms/manifest.ui \ res/forms/commit_revision.ui \ res/forms/application_update.ui \ - res/forms/file_history.ui + res/forms/file_history.ui \ + res/forms/unaccounted_renames.ui UI_DIR = tmp ============================================================ --- res/forms/main_window.ui 9f9f41d6a2d521f10475aabdd7e2901b29243dd1 +++ res/forms/main_window.ui 600472d44fd5cd51100da93909a592d1815e3505 @@ -187,6 +187,7 @@ + @@ -459,14 +460,14 @@ Ctrl+R + + + Find unaccounted renames + + - Splitter - QSplitter -
../Splitter.h
-
- InventoryView QTreeView
../InventoryView.h
@@ -476,6 +477,11 @@ QTreeView
../AttributesView.h
+ + Splitter + QSplitter +
../Splitter.h
+
============================================================ --- src/model/Inventory.cpp 1e554a233746a80cf81f5bac48efd24eeca7b873 +++ src/model/Inventory.cpp 3cceb0b22340f61c7beba6c6d2e05392d1839cc2 @@ -439,19 +439,22 @@ bool Inventory::handleError(int errCode) return true; } -QMap Inventory::findUnaccountedRenames() +QMap Inventory::findUnaccountedRenames() { QList missingItems; + QList unknownItems; foreach (InventoryItem * item, flatItemList) { - // if an item was just added, but is now missing, we have no fileid - // from the base roster, thus cannot find any matches later on - if (item->hasStatus(InventoryItem::Missing) && - item->hasNotStatus(InventoryItem::Added)) + if (item->hasStatus(InventoryItem::Missing)) { missingItems.append(item); } + + if (item->hasStatus(InventoryItem::Unknown)) + { + unknownItems.append(item); + } } // TODO: progess bar here! @@ -460,34 +463,56 @@ QMap Inventory:: FileEntryList parentList = MonotoneDelegate::getRevisionManifest(this, parentRev); - QMap unaccountedRenames; + QMap unaccountedRenames; + QMap fileIds; - foreach (InventoryItem * item, missingItems) + foreach (InventoryItem * missingItem, missingItems) { FileEntryList candidates; + FileEntry entry; + // this is a new entry not recorded in the base roster + if (missingItem->hasStatus(InventoryItem::Added)) + { + entry.path = missingItem->getPath(); + entry.is_dir = missingItem->isDirectory(); + } + else + { + bool found = false; + foreach (entry, parentList) + { + if (entry.path == missingItem->getPath()) + { + found = true; + break; + } + } + I(found); + } + // at first check if the simple case: the item was renamed // in the bookkeeping dir, but not physically - if (item->hasStatus(InventoryItem::RenamedTo)) + if (missingItem->hasStatus(InventoryItem::RenamedTo)) { - InventoryItem * oldItem = item->getRenamedFrom(); + InventoryItem * oldItem = missingItem->getRenamedFrom(); I(oldItem); // yes, there is an unknown path with the same name! if (oldItem->hasStatus(InventoryItem::Unknown)) { bool found = false; - FileEntry entry; - foreach (entry, parentList) + FileEntry oldEntry; + foreach (oldEntry, parentList) { - if (entry.path == oldItem->getPath()) + if (oldEntry.path == oldItem->getPath()) { found = true; break; } } I(found); - candidates.append(entry); + candidates.append(oldEntry); } else { @@ -496,31 +521,54 @@ QMap Inventory:: } else { - // apparently there are is rename info, lets try to find - // the source item "the hard way" - - // TODO: do a path-based comparison for directory items - // since they have no associated fileid - bool found = false; - FileEntry entry; - foreach (entry, parentList) + foreach (InventoryItem * unknownItem, unknownItems) { - if (entry.path == item->getPath()) + QString unknownPath = unknownItem->getPath(); + + // calculate the file id of the unknown file + if (!unknownItem->isDirectory() && + !fileIds.contains(unknownPath)) { - found = true; - break; + QString fileid = MonotoneDelegate::getFileId(this, unknownPath); + // file was not readable, etc. + if (fileid.isEmpty()) continue; + fileIds.insert(unknownPath, fileid); } + + // at first do a simple file name check + if ((missingItem->getFilename() == unknownItem->getFilename()) && + (missingItem->isDirectory() == unknownItem->isDirectory())) + { + FileEntry can(unknownPath, unknownItem->isDirectory()); + if (fileIds.contains(unknownPath)) + { + can.fileid = fileIds.value(unknownPath); + } + candidates.append(can); + continue; + } + + // we can't do anything for directories from here on + if (missingItem->isDirectory() || unknownItem->isDirectory()) + continue; + + D(unknownPath); + // we now rely on the fact that we have a fileid + I(fileIds.contains(unknownPath)); + + if (fileIds.value(unknownPath) == entry.fileid) + { + candidates.append(FileEntry(unknownPath, false, entry.fileid)); + } } - I(found); - - // TODO: for this to work however, we need some kind of - // mtn automate ident command - W(QString("FIXME: iterate over current workspace and " - "find suitable files with %1 for %2") - .arg(entry.fileid).arg(entry.path)); } - unaccountedRenames.insert(item->getPath(), candidates); + // only add those missing items to the map which are having any + // candidates, i.e. are likely to be renamed + if (candidates.size() > 0) + { + unaccountedRenames.insert(entry, candidates); + } } return unaccountedRenames; ============================================================ --- src/model/Inventory.h cb7bd39962c8f983a04c7410318e1643fb048ede +++ src/model/Inventory.h c9834d77c69b3b9d7c6dc35a5a8bc6118a5b01ec @@ -37,7 +37,7 @@ class Inventory : public QAbstractItemMo Inventory(QObject *parent); ~Inventory(); bool readInventory(); - QMap findUnaccountedRenames(); + QMap findUnaccountedRenames(); // needed Qt Model methods QVariant data(const QModelIndex&, int) const; ============================================================ --- src/monotone/Monotone.cpp 0fe70345373d6aa6338a8a237f7d0ddda2a2d308 +++ src/monotone/Monotone.cpp e064e50d6f2e22d1c15cd325346311a4ab93e97c @@ -89,8 +89,8 @@ // - if not, we additionally check the interface version in case the user // compiled his own version with all needed commands // -const QString Monotone::RequiredProgramVersion = "0.34"; -const QString Monotone::RequiredInterfaceVersion = "4.1"; +const QString Monotone::RequiredProgramVersion = "0.35"; +const QString Monotone::RequiredInterfaceVersion = "4.2"; const int Monotone::StdioBufferSize = 50 * 1024 * 1024; const int Monotone::WaitForMonotoneStart = 15000; // milliseconds ============================================================ --- src/monotone/MonotoneDelegate.cpp 6fe7d984293395f5d83ffdcf0c6735d93186a6f7 +++ src/monotone/MonotoneDelegate.cpp 0b1b0ebfaa468cbf89d3ef32a54e9791405b86f5 @@ -393,3 +393,23 @@ QStringList MonotoneDelegate::getPrivate } return keys; } + +QString MonotoneDelegate::getFileId(QObject * obj, const QString & path) +{ + Monotone * mtn = MTN(obj); + + int cmdNum; + mtn->executeCommand(QStringList() << "identify" << path, cmdNum); + + QString data = mtn->getDecodedData(cmdNum); + + if (mtn->getReturnCode(cmdNum) > 0) + { + C(QString("Couldn't identify path: %1").arg(data)); + return QString(); + } + + data.chop(1); + return data; +} + ============================================================ --- src/monotone/MonotoneDelegate.h 4a188eda03b4f743f508cdd05a333102dfeb0337 +++ src/monotone/MonotoneDelegate.h 6971e5c0d79d50fc766f84fb395cf648da80c8e5 @@ -47,6 +47,7 @@ public: static QString getDatabaseFilePath(QObject *); static FileEntryList getRevisionManifest(QObject *, const QString & rev = QString()); static QStringList getPrivateKeyList(QObject *); + static QString getFileId(QObject *, const QString &); private: AutomateCommand * cmdModel; ============================================================ --- src/view/MainWindow.cpp a12fb6d3645a244dadb84a4e44c95bf2f203dae6 +++ src/view/MainWindow.cpp 7064dcc81b11c6991521f64da0762beb9a347350 @@ -38,6 +38,7 @@ #include "Settings.h" #include "ChangesetBrowser.h" #include "WorkspaceCreator.h" +#include "UnaccountedRenames.h" #include "Guitone.h" #ifdef Q_WS_MAC @@ -659,3 +660,21 @@ void MainWindow::on_actionReload_workspa if (!ret) C("Could not read inventory."); } +void MainWindow::on_actionFind_unaccounted_renames_triggered() +{ + QMap renames = invModel->findUnaccountedRenames(); + if (renames.size() == 0) + { + QMessageBox::information( + this, + tr("Nothing found"), + tr("No unaccounted renames found for this workspace."), + QMessageBox::Ok + ); + return; + } + + UnaccountedRenames dlg(this, renames); + dlg.execDocumentModal(); +} + ============================================================ --- src/view/MainWindow.h 38296ee6b6bbd5e84836949b27167abccb20bb6a +++ src/view/MainWindow.h ed423769deea41872ede542b5920a765f790a38a @@ -75,6 +75,7 @@ private slots: void on_actionBring_all_to_front_triggered(); void on_actionCheck_for_updates_triggered(); void on_actionReload_workspace_triggered(); + void on_actionFind_unaccounted_renames_triggered(); void openRecentWorkspace(); void openRecentDatabase(); ============================================================ --- src/view/dialogs/FileHistory.cpp fb8b038667a771cbaee11c0f60d1269104e774d3 +++ src/view/dialogs/FileHistory.cpp 47ab8b46f70a4e81ecadd0c686f00dc8874c3fde @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2006 by Thomas Keller * + * Copyright (C) 2007 by Thomas Keller * * address@hidden * * * * This program is free software; you can redistribute it and/or modify * ============================================================ --- src/vocab.h 65c921759f5565377c8ffe49fb8abbfbb43fe27c +++ src/vocab.h ed37b5f6deab84d3025ff1bfb2b0341993391751 @@ -46,6 +46,11 @@ struct FileEntry { bool is_dir; QString fileid; QMap attrs; + + inline bool operator<(const FileEntry & other) const + { + return path < other.path; + } }; typedef QList FileEntryList;