Changeset 88686 in vbox for trunk/src/VBox
- Timestamp:
- Apr 23, 2021 6:55:55 PM (4 years ago)
- Location:
- trunk/src/VBox/Frontends/VirtualBox/src
- Files:
-
- 5 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/VBox/Frontends/VirtualBox/src/logviewer/UIVMLogPage.cpp
r88671 r88686 331 331 m_pTextEdit->setCurrentFont(font); 332 332 } 333 334 const QUuid &UIVMLogPage::machineId() const 335 { 336 return m_machineId; 337 } 338 339 void UIVMLogPage::setMachineId(const QUuid &machineId) 340 { 341 m_machineId = machineId; 342 } -
trunk/src/VBox/Frontends/VirtualBox/src/logviewer/UIVMLogPage.h
r88671 r88686 114 114 void setCurrentFont(QFont font); 115 115 116 const QUuid &machineId() const; 117 void setMachineId(const QUuid &machineId); 118 116 119 private slots: 117 120 … … 157 160 int m_iUnfilteredLineCount; 158 161 /** @} */ 159 162 /** Id of the machine the log shown in this page belongs to. */ 163 QUuid m_machineId; 160 164 }; 161 165 -
trunk/src/VBox/Frontends/VirtualBox/src/logviewer/UIVMLogViewerWidget.cpp
r88671 r88686 198 198 void UIVMLogViewerWidget::setSelectedVMListItems(const QList<UIVirtualMachineItem*> &items) 199 199 { 200 m_machines.clear(); 200 QVector<QUuid> selectedMachines; 201 /* List of machines that are newly added to selected machine list: */ 202 QVector<QUuid> newSelections; 203 201 204 foreach (const UIVirtualMachineItem *item, items) 202 205 { 203 206 if (!item) 204 207 continue; 205 //m_machines << Machine(item->id(), item->name()); 206 } 207 //updateMachineSelectionMenu(); 208 selectedMachines << item->id(); 209 } 210 211 QVector<QUuid> unselectedMachines(m_machines); 212 213 foreach (const QUuid &id, selectedMachines) 214 { 215 unselectedMachines.removeAll(id); 216 if (!m_machines.contains(id)) 217 newSelections << id; 218 } 219 m_machines = selectedMachines; 220 221 m_pTabWidget->hide(); 222 /* Read logs and create pages/tabs for newly selected machines: */ 223 createLogViewerPages(newSelections); 224 /* Remove the log pages/tabs of unselected machines from the tab widget: */ 225 removeLogViewerPages(unselectedMachines); 226 m_pTabWidget->show(); 227 228 printf("new selections\n"); 229 foreach (const QUuid &id, newSelections) 230 printf("%s\n", qPrintable(id.toString())); 231 printf("unselected\n"); 232 foreach (const QUuid &id, unselectedMachines) 233 printf("%s\n", qPrintable(id.toString())); 208 234 } 209 235 … … 233 259 void UIVMLogViewerWidget::sltRefresh() 234 260 { 235 if (!m_pTabWidget) 236 return; 237 /* Disconnect this connection to avoid initial signals during page creation/deletion: */ 238 disconnect(m_pTabWidget, &QITabWidget::currentChanged, m_pFilterPanel, &UIVMLogViewerFilterPanel::applyFilter); 239 disconnect(m_pTabWidget, &QITabWidget::currentChanged, this, &UIVMLogViewerWidget::sltTabIndexChange); 240 241 m_logPageList.clear(); 242 m_pTabWidget->setEnabled(true); 243 int currentTabIndex = m_pTabWidget->currentIndex(); 244 /* Hide the container widget during updates to avoid flickering: */ 245 m_pTabWidget->hide(); 246 QVector<QVector<LogBookmark> > logPageBookmarks; 247 /* Clear the tab widget. This might be an overkill but most secure way to deal with the case where 248 number of the log files changes. Store the bookmark vectors before deleting the pages*/ 249 while (m_pTabWidget->count()) 250 { 251 QWidget *pFirstPage = m_pTabWidget->widget(0); 252 UIVMLogPage *pLogPage = qobject_cast<UIVMLogPage*>(pFirstPage); 253 if (pLogPage) 254 logPageBookmarks.push_back(pLogPage->bookmarkVector()); 255 m_pTabWidget->removeTab(0); 256 delete pFirstPage; 257 } 258 259 bool noLogsToShow = createLogViewerPages(); 260 261 /* Apply the filter settings: */ 262 if (m_pFilterPanel) 263 m_pFilterPanel->applyFilter(); 264 265 /* Restore the bookmarks: */ 266 if (!noLogsToShow) 267 { 268 for (int i = 0; i < m_pTabWidget->count(); ++i) 269 { 270 UIVMLogPage *pLogPage = qobject_cast<UIVMLogPage*>(m_pTabWidget->widget(i)); 271 if (pLogPage && i < logPageBookmarks.size()) 272 pLogPage->setBookmarkVector(logPageBookmarks[i]); 273 } 274 } 275 276 /* Setup this connection after refresh to avoid initial signals during page creation: */ 277 if (m_pFilterPanel) 278 connect(m_pTabWidget, &QITabWidget::currentChanged, m_pFilterPanel, &UIVMLogViewerFilterPanel::applyFilter); 279 connect(m_pTabWidget, &QITabWidget::currentChanged, this, &UIVMLogViewerWidget::sltTabIndexChange); 280 281 /* Show the first tab widget's page after the refresh: */ 282 int tabIndex = (currentTabIndex < m_pTabWidget->count()) ? currentTabIndex : 0; 283 m_pTabWidget->setCurrentIndex(tabIndex); 284 sltTabIndexChange(tabIndex); 285 286 /* Enable/Disable toolbar actions (except Refresh) & tab widget according log presence: */ 287 m_pActionPool->action(UIActionIndex_M_Log_T_Find)->setEnabled(!noLogsToShow); 288 m_pActionPool->action(UIActionIndex_M_Log_T_Filter)->setEnabled(!noLogsToShow); 289 m_pActionPool->action(UIActionIndex_M_Log_S_Save)->setEnabled(!noLogsToShow); 290 m_pActionPool->action(UIActionIndex_M_Log_T_Bookmark)->setEnabled(!noLogsToShow); 291 m_pActionPool->action(UIActionIndex_M_Log_T_Options)->setEnabled(!noLogsToShow); 292 293 m_pTabWidget->show(); 294 if (m_pSearchPanel && m_pSearchPanel->isVisible()) 295 m_pSearchPanel->refresh(); 296 297 /* If there are no log files to show the hide all the open panels: */ 298 if (noLogsToShow) 299 { 300 for (QMap<UIDialogPanel*, QAction*>::iterator iterator = m_panelActionMap.begin(); 301 iterator != m_panelActionMap.end(); ++iterator) 302 { 303 if (iterator.key()) 304 hidePanel(iterator.key()); 305 } 306 } 261 // if (!m_pTabWidget) 262 // return; 263 // /* Disconnect this connection to avoid initial signals during page creation/deletion: */ 264 // disconnect(m_pTabWidget, &QITabWidget::currentChanged, m_pFilterPanel, &UIVMLogViewerFilterPanel::applyFilter); 265 // disconnect(m_pTabWidget, &QITabWidget::currentChanged, this, &UIVMLogViewerWidget::sltTabIndexChange); 266 267 // m_logPageList.clear(); 268 // m_pTabWidget->setEnabled(true); 269 // int currentTabIndex = m_pTabWidget->currentIndex(); 270 // /* Hide the container widget during updates to avoid flickering: */ 271 // m_pTabWidget->hide(); 272 // QVector<QVector<LogBookmark> > logPageBookmarks; 273 // /* Clear the tab widget. This might be an overkill but most secure way to deal with the case where 274 // number of the log files changes. Store the bookmark vectors before deleting the pages*/ 275 // while (m_pTabWidget->count()) 276 // { 277 // QWidget *pFirstPage = m_pTabWidget->widget(0); 278 // UIVMLogPage *pLogPage = qobject_cast<UIVMLogPage*>(pFirstPage); 279 // if (pLogPage) 280 // logPageBookmarks.push_back(pLogPage->bookmarkVector()); 281 // m_pTabWidget->removeTab(0); 282 // delete pFirstPage; 283 // } 284 285 // bool noLogsToShow = createLogViewerPages(); 286 287 // /* Apply the filter settings: */ 288 // if (m_pFilterPanel) 289 // m_pFilterPanel->applyFilter(); 290 291 // /* Restore the bookmarks: */ 292 // if (!noLogsToShow) 293 // { 294 // for (int i = 0; i < m_pTabWidget->count(); ++i) 295 // { 296 // UIVMLogPage *pLogPage = qobject_cast<UIVMLogPage*>(m_pTabWidget->widget(i)); 297 // if (pLogPage && i < logPageBookmarks.size()) 298 // pLogPage->setBookmarkVector(logPageBookmarks[i]); 299 // } 300 // } 301 302 // /* Setup this connection after refresh to avoid initial signals during page creation: */ 303 // if (m_pFilterPanel) 304 // connect(m_pTabWidget, &QITabWidget::currentChanged, m_pFilterPanel, &UIVMLogViewerFilterPanel::applyFilter); 305 // connect(m_pTabWidget, &QITabWidget::currentChanged, this, &UIVMLogViewerWidget::sltTabIndexChange); 306 307 // /* Show the first tab widget's page after the refresh: */ 308 // int tabIndex = (currentTabIndex < m_pTabWidget->count()) ? currentTabIndex : 0; 309 // m_pTabWidget->setCurrentIndex(tabIndex); 310 // sltTabIndexChange(tabIndex); 311 312 // /* Enable/Disable toolbar actions (except Refresh) & tab widget according log presence: */ 313 // m_pActionPool->action(UIActionIndex_M_Log_T_Find)->setEnabled(!noLogsToShow); 314 // m_pActionPool->action(UIActionIndex_M_Log_T_Filter)->setEnabled(!noLogsToShow); 315 // m_pActionPool->action(UIActionIndex_M_Log_S_Save)->setEnabled(!noLogsToShow); 316 // m_pActionPool->action(UIActionIndex_M_Log_T_Bookmark)->setEnabled(!noLogsToShow); 317 // m_pActionPool->action(UIActionIndex_M_Log_T_Options)->setEnabled(!noLogsToShow); 318 319 // m_pTabWidget->show(); 320 // if (m_pSearchPanel && m_pSearchPanel->isVisible()) 321 // m_pSearchPanel->refresh(); 322 323 // /* If there are no log files to show the hide all the open panels: */ 324 // if (noLogsToShow) 325 // { 326 // for (QMap<UIDialogPanel*, QAction*>::iterator iterator = m_panelActionMap.begin(); 327 // iterator != m_panelActionMap.end(); ++iterator) 328 // { 329 // if (iterator.key()) 330 // hidePanel(iterator.key()); 331 // } 332 // } 333 307 334 } 308 335 … … 460 487 m_bShowLineNumbers = bShowLineNumbers; 461 488 /* Set all log page instances. */ 462 for (int i = 0; i < m_logPageList.size(); ++i)463 { 464 UIVMLogPage* pLogPage = qobject_cast<UIVMLogPage*>(m_logPageList[i]);489 for (int i = 0; m_pTabWidget && (i < m_pTabWidget->count()); ++i) 490 { 491 UIVMLogPage* pLogPage = logPage(i); 465 492 if (pLogPage) 466 493 pLogPage->setShowLineNumbers(m_bShowLineNumbers); … … 475 502 m_bWrapLines = bWrapLines; 476 503 /* Set all log page instances. */ 477 for (int i = 0; i < m_logPageList.size(); ++i)478 { 479 UIVMLogPage* pLogPage = qobject_cast<UIVMLogPage*>(m_logPageList[i]);504 for (int i = 0; m_pTabWidget && (i < m_pTabWidget->count()); ++i) 505 { 506 UIVMLogPage* pLogPage = logPage(i); 480 507 if (pLogPage) 481 508 pLogPage->setWrapLines(m_bWrapLines); … … 488 515 return; 489 516 m_font.setPointSize(fontSize); 490 for (int i = 0; i < m_logPageList.size(); ++i)491 { 492 UIVMLogPage* pLogPage = qobject_cast<UIVMLogPage*>(m_logPageList[i]);517 for (int i = 0; m_pTabWidget && (i < m_pTabWidget->count()); ++i) 518 { 519 UIVMLogPage* pLogPage = logPage(i); 493 520 if (pLogPage) 494 521 pLogPage->setCurrentFont(m_font); … … 501 528 return; 502 529 m_font = font; 503 for (int i = 0; i < m_logPageList.size(); ++i)504 { 505 UIVMLogPage* pLogPage = qobject_cast<UIVMLogPage*>(m_logPageList[i]);530 for (int i = 0; m_pTabWidget && (i < m_pTabWidget->count()); ++i) 531 { 532 UIVMLogPage* pLogPage = logPage(i); 506 533 if (pLogPage) 507 534 pLogPage->setCurrentFont(m_font); … … 842 869 } 843 870 844 void UIVMLogViewerWidget::createLogPage(const QString &strFileName, const QString &strLogContent, bool noLogsToShow /* = false */) 871 void UIVMLogViewerWidget::createLogPage(const QString &strFileName, const QString &strMachineName, 872 const QUuid &machineId, 873 const QString &strLogContent, bool noLogsToShow /* = false */) 845 874 { 846 875 if (!m_pTabWidget) … … 848 877 849 878 /* Create page-container: */ 850 UIVMLogPage* pLogPage = new UIVMLogPage( );879 UIVMLogPage* pLogPage = new UIVMLogPage(this); 851 880 if (pLogPage) 852 881 { … … 857 886 pLogPage->setWrapLines(m_bWrapLines); 858 887 pLogPage->setCurrentFont(m_font); 859 888 pLogPage->setMachineId(machineId); 860 889 /* Set the file name only if we really have log file to read. */ 861 890 if (!noLogsToShow) … … 863 892 864 893 /* Add page-container to viewer-container: */ 865 int tabIndex = m_pTabWidget->insertTab(m_pTabWidget->count(), pLogPage, QFileInfo(strFileName).fileName()); 866 867 m_logPageList.resize(m_pTabWidget->count()); 868 m_logPageList[tabIndex] = pLogPage; 894 bool fTitleWithMachineName = true; 895 QString strTabTitle; 896 if (fTitleWithMachineName) 897 { 898 strTabTitle.append(strMachineName); 899 strTabTitle.append(" - "); 900 } 901 strTabTitle.append(QFileInfo(strFileName).fileName()); 902 m_pTabWidget->insertTab(m_pTabWidget->count(), pLogPage, strTabTitle); 869 903 870 904 /* Set text edit since we want to display this text: */ … … 887 921 const UIVMLogPage *UIVMLogViewerWidget::currentLogPage() const 888 922 { 889 int currentTabIndex = m_pTabWidget->currentIndex(); 890 if (currentTabIndex >= m_logPageList.size()) 923 if (!m_pTabWidget) 891 924 return 0; 892 return qobject_cast<const UIVMLogPage*>(m_ logPageList.at(currentTabIndex));925 return qobject_cast<const UIVMLogPage*>(m_pTabWidget->currentWidget()); 893 926 } 894 927 895 928 UIVMLogPage *UIVMLogViewerWidget::currentLogPage() 896 929 { 897 int currentTabIndex = m_pTabWidget->currentIndex(); 898 if (currentTabIndex >= m_logPageList.size() || currentTabIndex == -1) 930 if (!m_pTabWidget) 899 931 return 0; 900 901 return qobject_cast<UIVMLogPage*>(m_logPageList.at(currentTabIndex)); 902 } 903 904 bool UIVMLogViewerWidget::createLogViewerPages() 932 return qobject_cast<UIVMLogPage*>(m_pTabWidget->currentWidget()); 933 } 934 935 UIVMLogPage *UIVMLogViewerWidget::logPage(int iIndex) 936 { 937 if (!m_pTabWidget) 938 return 0; 939 return qobject_cast<UIVMLogPage*>(m_pTabWidget->widget(iIndex)); 940 } 941 942 bool UIVMLogViewerWidget::createLogViewerPages(const QVector<QUuid> &machineList) 905 943 { 906 944 bool noLogsToShow = false; … … 908 946 QString strDummyTabText; 909 947 /* check if the machine is valid: */ 910 if (m_comMachine.isNull())911 {912 noLogsToShow = true;913 strDummyTabText = QString(tr("<p><b>No machine</b> is currently selected or the selected machine is not valid. "914 "Please select a Virtual Machine to see its logs"));915 }948 // if (m_comMachine.isNull()) 949 // { 950 // noLogsToShow = true; 951 // strDummyTabText = QString(tr("<p><b>No machine</b> is currently selected or the selected machine is not valid. " 952 // "Please select a Virtual Machine to see its logs")); 953 // } 916 954 917 955 const CSystemProperties &sys = uiCommon().virtualBox().GetSystemProperties(); 918 956 unsigned cMaxLogs = sys.GetLogHistoryCount() + 1 /*VBox.log*/ + 1 /*VBoxHardening.log*/; /** @todo Add api for getting total possible log count! */ 919 bool logFileRead = false; 920 for (unsigned i = 0; i < cMaxLogs && !noLogsToShow; ++i) 921 { 922 /* Query the log file name for index i: */ 923 QString strFileName = m_comMachine.QueryLogFilename(i); 924 if (!strFileName.isEmpty()) 925 { 926 /* Try to read the log file with the index i: */ 927 ULONG uOffset = 0; 928 QString strText; 929 while (true) 957 //bool logFileRead = false; 958 foreach (const QUuid &machineId, machineList) 959 { 960 CMachine comMachine = uiCommon().virtualBox().FindMachine(machineId.toString()); 961 962 if (comMachine.isNull()) 963 continue; 964 for (unsigned i = 0; i < cMaxLogs && !noLogsToShow; ++i) 965 { 966 /* Query the log file name for index i: */ 967 QString strFileName = comMachine.QueryLogFilename(i); 968 if (!strFileName.isEmpty()) 930 969 { 931 QVector<BYTE> data = m_comMachine.ReadLog(i, uOffset, _1M); 932 if (data.size() == 0) 933 break; 934 strText.append(QString::fromUtf8((char*)data.data(), data.size())); 935 uOffset += data.size(); 936 /* Don't read futher if we have reached the allowed size limit: */ 937 if (uOffset >= uAllowedLogSize) 970 /* Try to read the log file with the index i: */ 971 ULONG uOffset = 0; 972 QString strText; 973 while (true) 938 974 { 939 strText.append("\n=========Log file has been truncated as it is too large.======"); 940 break; 975 QVector<BYTE> data = comMachine.ReadLog(i, uOffset, _1M); 976 if (data.size() == 0) 977 break; 978 strText.append(QString::fromUtf8((char*)data.data(), data.size())); 979 uOffset += data.size(); 980 /* Don't read futher if we have reached the allowed size limit: */ 981 if (uOffset >= uAllowedLogSize) 982 { 983 strText.append("\n=========Log file has been truncated as it is too large.======"); 984 break; 985 } 986 } 987 /* Anything read at all? */ 988 if (uOffset > 0) 989 { 990 //logFileRead = true; 991 createLogPage(strFileName, comMachine.GetName(), comMachine.GetId(), strText); 941 992 } 942 993 } 943 /* Anything read at all? */ 944 if (uOffset > 0) 945 { 946 logFileRead = true; 947 createLogPage(strFileName, strText); 948 } 949 } 950 } 951 if (!noLogsToShow && !logFileRead) 952 { 953 noLogsToShow = true; 954 strDummyTabText = QString(tr("<p>No log files found. Press the " 955 "<b>Refresh</b> button to rescan the log folder " 956 "<nobr><b>%1</b></nobr>.</p>") 957 .arg(m_comMachine.GetLogFolder())); 958 } 994 } 995 } 996 // if (!noLogsToShow && !logFileRead) 997 // { 998 // noLogsToShow = true; 999 // // strDummyTabText = QString(tr("<p>No log files found. Press the " 1000 // // "<b>Refresh</b> button to rescan the log folder " 1001 // // "<nobr><b>%1</b></nobr>.</p>") 1002 // // .arg(m_comMachine.GetLogFolder())); 1003 // } 959 1004 960 1005 /* if noLogsToShow then ceate a single log page with an error message: */ 961 if (noLogsToShow)962 {963 createLogPage("No Logs", strDummyTabText, noLogsToShow);964 }1006 // if (noLogsToShow) 1007 // { 1008 // createLogPage("No Logs", strDummyTabText, noLogsToShow); 1009 // } 965 1010 return noLogsToShow; 966 1011 } 1012 1013 void UIVMLogViewerWidget::removeLogViewerPages(const QVector<QUuid> &machineList) 1014 { 1015 /* Nothing to do: */ 1016 if (machineList.isEmpty() || !m_pTabWidget) 1017 return; 1018 m_pTabWidget->blockSignals(true); 1019 /* Cache log page pointers and tab titles: */ 1020 QVector<QPair<UIVMLogPage*, QString> > logPages; 1021 for (int i = 0; i < m_pTabWidget->count(); ++i) 1022 { 1023 UIVMLogPage *pTab = logPage(i); 1024 if (pTab) 1025 logPages << QPair<UIVMLogPage*, QString>(pTab, m_pTabWidget->tabText(i)); 1026 } 1027 /* Remove all the tabs from tab widget, note that this does not delete tab widgets: */ 1028 m_pTabWidget->clear(); 1029 /* Add tab widgets (log pages) back as long as machine id is not in machineList: */ 1030 for (int i = 0; i < logPages.size(); ++i) 1031 { 1032 if (!logPages[i].first) 1033 continue; 1034 const QUuid &id = logPages[i].first->machineId(); 1035 1036 if (machineList.contains(id)) 1037 continue; 1038 m_pTabWidget->addTab(logPages[i].first, logPages[i].second); 1039 } 1040 m_pTabWidget->blockSignals(false); 1041 } 1042 967 1043 968 1044 void UIVMLogViewerWidget::resetHighlighthing() -
trunk/src/VBox/Frontends/VirtualBox/src/logviewer/UIVMLogViewerWidget.h
r88671 r88686 173 173 QPlainTextEdit* logPage(int pIndex) const; 174 174 /** Returns the newly created log-page using @a strPage filename. */ 175 void createLogPage(const QString &strFileName, const QString &strLogContent, bool noLogsToShow = false); 175 void createLogPage(const QString &strFileName, const QString &strMachineName, 176 const QUuid &machineId, 177 const QString &strLogContent, bool noLogsToShow = false); 176 178 177 179 const UIVMLogPage *currentLogPage() const; 178 180 UIVMLogPage *currentLogPage(); 179 180 /** Attempts to read the logs through the API, returns true if there exists any logs, false otherwise. */ 181 bool createLogViewerPages(); 181 /** Returns the log page at tab with iIndex if it contains a log page. Return 0 otherwise. */ 182 UIVMLogPage *logPage(int iIndex); 183 184 /** Attempts to read the logs through the API, returns true if there exists any logs, false otherwise. 185 * @p machineList is the list of machine whose log should be read. */ 186 bool createLogViewerPages(const QVector<QUuid> &machineList); 187 /** Removes the log pages/tabs that shows logs of the machines from @p machineList. */ 188 void removeLogViewerPages(const QVector<QUuid> &machineList); 182 189 183 190 /** Resets document (of the curent tab) and scrollbar highligthing */ … … 201 208 /** Holds the machine instance. */ 202 209 CMachine m_comMachine; 203 Q Map<QUuid, CMachine> m_machines;210 QVector<QUuid> m_machines; 204 211 205 212 /** Holds whether the dialog is polished. */ … … 208 215 /** Holds container for log-pages. */ 209 216 QITabWidget *m_pTabWidget; 210 /** Stores the UIVMLogPage instances. This is modified as we add and remove new tabs211 * to the m_pTabWidget. Index is the index of the tab widget. */212 QVector<QWidget*> m_logPageList;213 217 214 218 /** @name Panel instances and a QMap for mapping panel instances to related actions. -
trunk/src/VBox/Frontends/VirtualBox/src/manager/UIToolPaneMachine.cpp
r88644 r88686 183 183 /* Configure pane: */ 184 184 m_pPaneLogViewer->setProperty("ToolType", QVariant::fromValue(UIToolType_Logs)); 185 m_pPaneLogViewer->set Machine(m_comMachine);185 m_pPaneLogViewer->setSelectedVMListItems(m_items); 186 186 187 187 /* Add into layout: */ … … 298 298 m_pPaneSnapshots->setMachine(m_comMachine); 299 299 } 300 /* Update logviewer pane is it is open: */301 if (isToolOpened(UIToolType_Logs))302 {303 AssertPtrReturnVoid(m_pPaneLogViewer);304 m_pPaneLogViewer->setMachine(m_comMachine);305 }306 300 /* Update performance monitor pane is it is open: */ 307 301 if (isToolOpened(UIToolType_VMActivity))
Note:
See TracChangeset
for help on using the changeset viewer.