Changeset 11739 in webkit for trunk/JavaScriptCore/kxmlcore/HashTable.h
- Timestamp:
- Dec 22, 2005, 5:52:43 PM (19 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/JavaScriptCore/kxmlcore/HashTable.h
r10653 r11739 25 25 26 26 #include "FastMalloc.h" 27 #include "HashTraits.h" 27 28 #include <utility> 29 #include <algorithm> 28 30 29 31 namespace KXMLCore { … … 53 55 #endif 54 56 55 template<typename Key, typename Value, Key ExtractKey(const Value &), typename HashFunctions, typenameTraits>57 template<typename Key, typename Value, const Key& ExtractKey(const Value &), typename HashFunctions, typename Traits, typename KeyTraits> 56 58 class HashTable; 57 59 58 template<typename Key, typename Value, Key ExtractKey(const Value &), typename HashFunctions, typenameTraits>60 template<typename Key, typename Value, const Key& ExtractKey(const Value &), typename HashFunctions, typename Traits, typename KeyTraits> 59 61 class HashTableIterator { 60 62 private: 61 typedef HashTable<Key, Value, ExtractKey, HashFunctions, Traits > HashTableType;62 typedef HashTableIterator<Key, Value, ExtractKey, HashFunctions, Traits > iterator;63 typedef HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits> HashTableType; 64 typedef HashTableIterator<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits> iterator; 63 65 typedef Value ValueType; 64 66 typedef ValueType& ReferenceType; 65 67 typedef ValueType *PointerType; 66 68 67 friend class HashTable<Key, Value, ExtractKey, HashFunctions, Traits >;69 friend class HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>; 68 70 69 71 void skipEmptyBuckets() … … 112 114 }; 113 115 114 template<typename Key, typename Value, Key ExtractKey(const Value &), typename HashFunctions, typenameTraits>116 template<typename Key, typename Value, const Key& ExtractKey(const Value &), typename HashFunctions, typename Traits, typename KeyTraits> 115 117 class HashTableConstIterator { 116 118 private: 117 typedef HashTable<Key, Value, ExtractKey, HashFunctions, Traits > HashTableType;118 typedef HashTableIterator<Key, Value, ExtractKey, HashFunctions, Traits > iterator;119 typedef HashTableConstIterator<Key, Value, ExtractKey, HashFunctions, Traits > const_iterator;119 typedef HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits> HashTableType; 120 typedef HashTableIterator<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits> iterator; 121 typedef HashTableConstIterator<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits> const_iterator; 120 122 typedef Value ValueType; 121 123 typedef const ValueType& ReferenceType; 122 124 typedef const ValueType *PointerType; 123 125 124 friend class HashTable<Key, Value, ExtractKey, HashFunctions, Traits >;126 friend class HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>; 125 127 126 128 HashTableConstIterator(PointerType position, PointerType endPosition) … … 171 173 }; 172 174 173 template<typename Key, typename Value, Key ExtractKey(const Value &), typename HashFunctions, typename Traits> 175 using std::swap; 176 177 // swap pairs by component, in case of pair members that specialize swap 178 template<typename T, typename U> 179 inline void swap(pair<T, U>& a, pair<T, U>& b) 180 { 181 swap(a.first, b.first); 182 swap(a.second, b.second); 183 } 184 185 template<typename T, bool useSwap> 186 class Mover; 187 188 template<typename T> 189 struct Mover<T, true> { 190 static void move(T& from, T& to) 191 { 192 swap(from, to); 193 } 194 }; 195 196 template<typename T> 197 struct Mover<T, false> { 198 static void move(T& from, T& to) 199 { 200 to = from; 201 } 202 }; 203 204 template<typename Key, typename Value, typename HashFunctions> 205 class IdentityHashTranslator { 206 207 public: 208 static unsigned hash(const Key& key) 209 { 210 return HashFunctions::hash(key); 211 } 212 213 static bool equal(const Key& a, const Key& b) 214 { 215 return HashFunctions::equal(a, b); 216 } 217 218 static void translate(Value& location, const Key& key, const Value& value, unsigned) 219 { 220 location = value; 221 } 222 }; 223 224 template<typename Key, typename Value, const Key& ExtractKey(const Value &), typename HashFunctions, typename Traits, typename KeyTraits> 174 225 class HashTable { 175 226 public: 176 typedef HashTableIterator<Key, Value, ExtractKey, HashFunctions, Traits > iterator;177 typedef HashTableConstIterator<Key, Value, ExtractKey, HashFunctions, Traits > const_iterator;227 typedef HashTableIterator<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits> iterator; 228 typedef HashTableConstIterator<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits> const_iterator; 178 229 typedef Key KeyType; 179 230 typedef Value ValueType; 231 typedef IdentityHashTranslator<Key, Value, HashFunctions> IdentityTranslatorType; 180 232 181 233 HashTable() : m_table(0), m_tableSize(0), m_tableSizeMask(0), m_keyCount(0), m_deletedCount(0) {} 182 ~HashTable() { fastFree(m_table); }234 ~HashTable() { deallocateTable(m_table, m_tableSize); } 183 235 184 236 HashTable(const HashTable& other); … … 194 246 int capacity() const { return m_tableSize; } 195 247 196 std::pair<iterator, bool> insert(const ValueType& value) { return insert<KeyType, ValueType, hash, equal, identityConvert>(extractKey(value), value); }248 pair<iterator, bool> insert(const ValueType& value) { return insert<KeyType, ValueType, IdentityTranslatorType>(ExtractKey(value), value); } 197 249 198 250 // a special version of insert() that finds the object by hashing and comparing 199 251 // with some other type, to avoid the cost of type conversion if the object is already 200 252 // in the table 201 template<typename T, typename Extra, unsigned HashT(const T&), bool EqualT(const Key&, const T&), ValueType ConvertT(const T&, const Extra &, unsigned)>202 std::pair<iterator, bool> insert(const T& key, const Extra& extra);253 template<typename T, typename Extra, typename HashTranslator> 254 pair<iterator, bool> insert(const T& key, const Extra& extra); 203 255 204 256 iterator find(const KeyType& key); … … 210 262 void clear(); 211 263 212 static bool isEmptyBucket(const ValueType& value) { return extractKey(value) == extractKey(Traits::emptyValue()); }213 static bool isDeletedBucket(const ValueType& value) { return extractKey(value) == extractKey(Traits::deletedValue()); }264 static bool isEmptyBucket(const ValueType& value) { return ExtractKey(value) == KeyTraits::emptyValue(); } 265 static bool isDeletedBucket(const ValueType& value) { return ExtractKey(value) == KeyTraits::deletedValue(); } 214 266 static bool isEmptyOrDeletedBucket(const ValueType& value) { return isEmptyBucket(value) || isDeletedBucket(value); } 215 267 216 268 private: 217 static unsigned hash(const KeyType& key) { return HashFunctions::hash(key); }218 static bool equal(const KeyType& a, const KeyType& b) { return HashFunctions::equal(a, b); }219 // FIXME: assumes key == value; special lookup needs adjusting220 static ValueType identityConvert(const KeyType& key, const ValueType& value, unsigned) { return value; }221 static KeyType extractKey(const ValueType& value) { return ExtractKey(value); }222 223 269 static ValueType *allocateTable(int size); 224 225 typedef std::pair<ValueType *, bool> LookupType; 226 typedef std::pair<LookupType, unsigned> FullLookupType; 227 228 LookupType lookup(const Key& key) { return lookup<Key, hash, equal>(key).first; } 229 template<typename T, unsigned HashT(const T&), bool EqualT(const Key&, const T&)> 270 static void deallocateTable(ValueType *table, int size); 271 272 typedef pair<ValueType *, bool> LookupType; 273 typedef pair<LookupType, unsigned> FullLookupType; 274 275 LookupType lookup(const Key& key) { return lookup<Key, IdentityTranslatorType>(key).first; } 276 template<typename T, typename HashTranslator> 230 277 FullLookupType lookup(const T&); 231 278 … … 239 286 240 287 void rehash(int newTableSize); 241 void reinsert( constValueType&);242 243 static void clearBucket(ValueType& key) { key = Traits::emptyValue();}244 static void deleteBucket(ValueType& key) { key = Traits::deletedValue();}288 void reinsert(ValueType&); 289 290 static void initializeBucket(ValueType& bucket) { new (&bucket) ValueType(Traits::emptyValue()); } 291 static void deleteBucket(ValueType& bucket) { assignDeleted<ValueType, Traits>(bucket); } 245 292 246 293 FullLookupType makeLookupResult(ValueType *position, bool found, unsigned hash) … … 268 315 }; 269 316 270 template<typename Key, typename Value, Key ExtractKey(const Value &), typename HashFunctions, typenameTraits>271 template<typename T, unsigned HashT(const T&), bool EqualT(const Key&, const T&)>272 inline typename HashTable<Key, Value, ExtractKey, HashFunctions, Traits >::FullLookupType HashTable<Key, Value, ExtractKey, HashFunctions,Traits>::lookup(const T& key)317 template<typename Key, typename Value, const Key& ExtractKey(const Value &), typename HashFunctions, typename Traits, typename KeyTraits> 318 template<typename T, typename HashTranslator> 319 inline typename HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>::FullLookupType HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>::lookup(const T& key) 273 320 { 274 321 assert(m_table); 275 322 276 unsigned h = HashT (key);323 unsigned h = HashTranslator::hash(key); 277 324 int sizeMask = m_tableSizeMask; 278 325 int i = h & sizeMask; … … 290 337 if (isDeletedBucket(*entry)) 291 338 deletedEntry = entry; 292 else if ( EqualT(extractKey(*entry), key))339 else if (HashTranslator::equal(ExtractKey(*entry), key)) 293 340 return makeLookupResult(entry, true, h); 294 341 #if DUMP_HASHTABLE_STATS … … 305 352 306 353 307 template<typename Key, typename Value, Key ExtractKey(const Value &), typename HashFunctions, typenameTraits>308 template<typename T, typename Extra, unsigned HashT(const T&), bool EqualT(const Key&, const T&), Value ConvertT(const T&, const Extra &, unsigned)>309 inline std::pair<typename HashTable<Key, Value, ExtractKey, HashFunctions, Traits>::iterator, bool> HashTable<Key, Value, ExtractKey, HashFunctions,Traits>::insert(const T& key, const Extra &extra)354 template<typename Key, typename Value, const Key& ExtractKey(const Value &), typename HashFunctions, typename Traits, typename KeyTraits> 355 template<typename T, typename Extra, typename HashTranslator> 356 inline pair<typename HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>::iterator, bool> HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>::insert(const T& key, const Extra &extra) 310 357 { 311 358 if (!m_table) … … 314 361 checkTableConsistency(); 315 362 316 FullLookupType lookupResult = lookup<T, HashT , EqualT>(key);363 FullLookupType lookupResult = lookup<T, HashTranslator>(key); 317 364 318 365 ValueType *entry = lookupResult.first.first; … … 327 374 --m_deletedCount; 328 375 329 *entry = ConvertT(key, extra, h);376 HashTranslator::translate(*entry, key, extra, h); 330 377 ++m_keyCount; 331 378 332 379 if (shouldExpand()) { 333 KeyType enteredKey = extractKey(*entry); 380 // FIXME: this makes an extra copy on expand. Probably not that bad since 381 // expand is rare, but would be better to have a version of expand that can 382 // follow a pivot entry and return the new position 383 KeyType enteredKey = ExtractKey(*entry); 334 384 expand(); 335 385 return std::make_pair(find(enteredKey), true); … … 341 391 } 342 392 343 template<typename Key, typename Value, Key ExtractKey(const Value &), typename HashFunctions, typenameTraits>344 inline void HashTable<Key, Value, ExtractKey, HashFunctions, Traits >::reinsert(constValueType& entry)393 template<typename Key, typename Value, const Key& ExtractKey(const Value &), typename HashFunctions, typename Traits, typename KeyTraits> 394 inline void HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>::reinsert(ValueType& entry) 345 395 { 346 396 assert(m_table); 347 assert(!lookup( extractKey(entry)).second);348 assert(!isDeletedBucket(*(lookup( extractKey(entry)).first)));397 assert(!lookup(ExtractKey(entry)).second); 398 assert(!isDeletedBucket(*(lookup(ExtractKey(entry)).first))); 349 399 #if DUMP_HASHTABLE_STATS 350 400 ++HashTableStats::numReinserts; 351 401 #endif 352 402 353 *(lookup(extractKey(entry)).first) = entry;354 } 355 356 template<typename Key, typename Value, Key ExtractKey(const Value &), typename HashFunctions, typenameTraits>357 inline typename HashTable<Key, Value, ExtractKey, HashFunctions, Traits >::iterator HashTable<Key, Value, ExtractKey, HashFunctions,Traits>::find(const Key& key)403 Mover<ValueType, Traits::needsDestruction>::move(entry, *(lookup(ExtractKey(entry)).first)); 404 } 405 406 template<typename Key, typename Value, const Key& ExtractKey(const Value &), typename HashFunctions, typename Traits, typename KeyTraits> 407 inline typename HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>::iterator HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>::find(const Key& key) 358 408 { 359 409 if (!m_table) … … 366 416 } 367 417 368 template<typename Key, typename Value, Key ExtractKey(const Value &), typename HashFunctions, typenameTraits>369 inline typename HashTable<Key, Value, ExtractKey, HashFunctions, Traits >::const_iterator HashTable<Key, Value, ExtractKey, HashFunctions,Traits>::find(const Key& key) const418 template<typename Key, typename Value, const Key& ExtractKey(const Value &), typename HashFunctions, typename Traits, typename KeyTraits> 419 inline typename HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>::const_iterator HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>::find(const Key& key) const 370 420 { 371 421 if (!m_table) … … 378 428 } 379 429 380 template<typename Key, typename Value, Key ExtractKey(const Value &), typename HashFunctions, typenameTraits>381 inline bool HashTable<Key, Value, ExtractKey, HashFunctions, Traits >::contains(const KeyType& key) const430 template<typename Key, typename Value, const Key& ExtractKey(const Value &), typename HashFunctions, typename Traits, typename KeyTraits> 431 inline bool HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>::contains(const KeyType& key) const 382 432 { 383 433 if (!m_table) … … 387 437 } 388 438 389 template<typename Key, typename Value, Key ExtractKey(const Value &), typename HashFunctions, typenameTraits>390 inline void HashTable<Key, Value, ExtractKey, HashFunctions, Traits >::remove(ValueType *pos)439 template<typename Key, typename Value, const Key& ExtractKey(const Value &), typename HashFunctions, typename Traits, typename KeyTraits> 440 inline void HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>::remove(ValueType *pos) 391 441 { 392 442 checkTableConsistency(); … … 406 456 } 407 457 408 template<typename Key, typename Value, Key ExtractKey(const Value &), typename HashFunctions, typenameTraits>409 inline void HashTable<Key, Value, ExtractKey, HashFunctions, Traits >::remove(const KeyType& key)458 template<typename Key, typename Value, const Key& ExtractKey(const Value &), typename HashFunctions, typename Traits, typename KeyTraits> 459 inline void HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>::remove(const KeyType& key) 410 460 { 411 461 if (!m_table) … … 415 465 } 416 466 417 template<typename Key, typename Value, Key ExtractKey(const Value &), typename HashFunctions, typenameTraits>418 inline void HashTable<Key, Value, ExtractKey, HashFunctions, Traits >::remove(iterator it)467 template<typename Key, typename Value, const Key& ExtractKey(const Value &), typename HashFunctions, typename Traits, typename KeyTraits> 468 inline void HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>::remove(iterator it) 419 469 { 420 470 if (it == end()) … … 425 475 426 476 427 template<typename Key, typename Value, Key ExtractKey(const Value &), typename HashFunctions, typenameTraits>428 inline Value *HashTable<Key, Value, ExtractKey, HashFunctions, Traits >::allocateTable(int size)477 template<typename Key, typename Value, const Key& ExtractKey(const Value &), typename HashFunctions, typename Traits, typename KeyTraits> 478 inline Value *HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>::allocateTable(int size) 429 479 { 430 480 // would use a template member function with explicit specializations here, but … … 435 485 ValueType *result = reinterpret_cast<ValueType *>(fastMalloc(size * sizeof(ValueType))); 436 486 for (int i = 0; i < size; i++) { 437 clearBucket(result[i]);487 initializeBucket(result[i]); 438 488 } 439 489 return result; 440 490 } 441 491 } 442 443 template<typename Key, typename Value, Key ExtractKey(const Value &), typename HashFunctions, typename Traits> 444 inline void HashTable<Key, Value, ExtractKey, HashFunctions, Traits>::expand() 492 493 template<typename Key, typename Value, const Key& ExtractKey(const Value &), typename HashFunctions, typename Traits, typename KeyTraits> 494 inline void HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>::deallocateTable(ValueType *table, int size) 495 { 496 if (Traits::needsDestruction) { 497 for (int i = 0; i < size; ++i) { 498 (&table[i])->~ValueType(); 499 } 500 } 501 502 fastFree(table); 503 } 504 505 template<typename Key, typename Value, const Key& ExtractKey(const Value &), typename HashFunctions, typename Traits, typename KeyTraits> 506 inline void HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>::expand() 445 507 { 446 508 int newSize; … … 455 517 } 456 518 457 template<typename Key, typename Value, Key ExtractKey(const Value &), typename HashFunctions, typenameTraits>458 void HashTable<Key, Value, ExtractKey, HashFunctions, Traits >::rehash(int newTableSize)519 template<typename Key, typename Value, const Key& ExtractKey(const Value &), typename HashFunctions, typename Traits, typename KeyTraits> 520 void HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>::rehash(int newTableSize) 459 521 { 460 522 checkTableConsistencyExceptSize(); … … 473 535 474 536 for (int i = 0; i != oldTableSize; ++i) { 475 ValueType entry = oldTable[i]; 476 if (!isEmptyOrDeletedBucket(entry)) 477 reinsert(entry); 537 if (!isEmptyOrDeletedBucket(oldTable[i])) 538 reinsert(oldTable[i]); 478 539 } 479 540 480 541 m_deletedCount = 0; 481 542 482 fastFree(oldTable);543 deallocateTable(oldTable, oldTableSize); 483 544 484 545 checkTableConsistency(); 485 546 } 486 547 487 template<typename Key, typename Value, Key ExtractKey(const Value &), typename HashFunctions, typenameTraits>488 inline void HashTable<Key, Value, ExtractKey, HashFunctions, Traits >::clear()489 { 490 fastFree(m_table);548 template<typename Key, typename Value, const Key& ExtractKey(const Value &), typename HashFunctions, typename Traits, typename KeyTraits> 549 inline void HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>::clear() 550 { 551 deallocateTable(m_table, m_tableSize); 491 552 m_table = 0; 492 553 m_tableSize = 0; … … 495 556 } 496 557 497 template<typename Key, typename Value, Key ExtractKey(const Value &), typename HashFunctions, typenameTraits>498 HashTable<Key, Value, ExtractKey, HashFunctions, Traits >::HashTable(const HashTable& other)558 template<typename Key, typename Value, const Key& ExtractKey(const Value &), typename HashFunctions, typename Traits, typename KeyTraits> 559 HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>::HashTable(const HashTable& other) 499 560 : m_table(0) 500 , m_tableSize(other.m_tableSize) 501 , m_tableSizeMask(other.m_tableSizeMask) 502 , m_keyCount(other.m_keyCount) 503 , m_deletedCount(other.m_deletedCount) 504 { 505 if (m_tableSize != 0) { 506 m_table = fastMalloc(m_tableSize); 507 memcpy(other.m_table, m_table, m_tableSize * sizeof(ValueType)); 508 } 509 } 510 511 template<typename Key, typename Value, Key ExtractKey(const Value &), typename HashFunctions, typename Traits> 512 void HashTable<Key, Value, ExtractKey, HashFunctions, Traits>::swap(const HashTable& other) 561 , m_tableSize(0) 562 , m_tableSizeMask(0) 563 , m_keyCount(0) 564 , m_deletedCount(0) 565 { 566 // doesn't matter if copying a hashtable is efficient so just 567 // do it the dumb way, by copying each element. 568 iterator end = other.end(); 569 for (iterator it = other.begin(); it != end; ++it) { 570 insert(*it); 571 } 572 } 573 574 template<typename Key, typename Value, const Key& ExtractKey(const Value &), typename HashFunctions, typename Traits, typename KeyTraits> 575 void HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>::swap(const HashTable& other) 513 576 { 514 577 ValueType *tmp_table = m_table; … … 533 596 } 534 597 535 template<typename Key, typename Value, Key ExtractKey(const Value &), typename HashFunctions, typenameTraits>536 HashTable<Key, Value, ExtractKey, HashFunctions, Traits >& HashTable<Key, Value, ExtractKey, HashFunctions,Traits>::operator=(const HashTable& other)598 template<typename Key, typename Value, const Key& ExtractKey(const Value &), typename HashFunctions, typename Traits, typename KeyTraits> 599 HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>& HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>::operator=(const HashTable& other) 537 600 { 538 601 HashTable tmp(other); … … 543 606 #if CHECK_HASHTABLE_CONSISTENCY 544 607 545 template<typename Key, typename Value, Key ExtractKey(const Value &), typename HashFunctions, typenameTraits>546 void HashTable<Key, Value, ExtractKey, HashFunctions, Traits >::checkTableConsistency() const608 template<typename Key, typename Value, const Key& ExtractKey(const Value &), typename HashFunctions, typename Traits, typename KeyTraits> 609 void HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>::checkTableConsistency() const 547 610 { 548 611 checkTableConsistencyExceptSize(); … … 551 614 } 552 615 553 template<typename Key, typename Value, Key ExtractKey(const Value &), typename HashFunctions, typenameTraits>554 void HashTable<Key, Value, ExtractKey, HashFunctions, Traits >::checkTableConsistencyExceptSize() const616 template<typename Key, typename Value, const Key& ExtractKey(const Value &), typename HashFunctions, typename Traits, typename KeyTraits> 617 void HashTable<Key, Value, ExtractKey, HashFunctions, Traits, KeyTraits>::checkTableConsistencyExceptSize() const 555 618 { 556 619 if (!m_table) … … 569 632 } 570 633 571 const_iterator it = find( extractKey(*entry));634 const_iterator it = find(ExtractKey(*entry)); 572 635 assert(entry == it.m_position); 573 636 ++count;
Note:
See TracChangeset
for help on using the changeset viewer.