Skip to content

Commit bb9493f

Browse files
xingyaowwopenhands-agentryanhoangt
authored
Rename get_last_history to pop_last_history and fix related issues (#85)
* Update FileHistoryManager to return actual metadata in get_metadata method * Update test_history.py * Rename get_last_history to pop_last_history for clarity * Add debug logging to FileHistoryManager and test file * rename method and fix lint * fix lint * fix intg test * add multiple undo test case * remove prints * bump version --------- Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: Hoang Tran <descience.thh10@gmail.com>
1 parent efbe502 commit bb9493f

File tree

7 files changed

+111
-33
lines changed

7 files changed

+111
-33
lines changed

openhands_aci/editor/editor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ def undo_edit(self, path: Path) -> CLIResult:
426426
Implement the undo_edit command.
427427
"""
428428
current_text = self.read_file(path).expandtabs()
429-
old_text = self._history_manager.get_last_history(path)
429+
old_text = self._history_manager.pop_last_history(path)
430430
if old_text is None:
431431
raise ToolError(f'No edit history found for {path}.')
432432

openhands_aci/editor/history.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,26 +58,38 @@ def add_history(self, file_path: Path, content: str):
5858

5959
self.cache.set(metadata_key, metadata)
6060

61-
def get_last_history(self, file_path: Path) -> Optional[str]:
62-
"""Get the most recent history entry for a file."""
61+
def pop_last_history(self, file_path: Path) -> Optional[str]:
62+
"""Pop and return the most recent history entry for a file."""
6363
metadata_key = self._get_metadata_key(file_path)
6464
metadata = self.cache.get(metadata_key, {'entries': [], 'counter': 0})
6565
entries = metadata['entries']
6666

6767
if not entries:
6868
return None
6969

70-
# Get the last entry without removing it
71-
last_counter = entries[-1]
70+
# Pop and remove the last entry
71+
last_counter = entries.pop()
7272
history_key = self._get_history_key(file_path, last_counter)
7373
content = self.cache.get(history_key)
7474

7575
if content is None:
7676
self.logger.warning(f'History entry not found for {file_path}')
77-
return None
77+
else:
78+
# Remove the entry from the cache
79+
self.cache.delete(history_key)
80+
81+
# Update metadata
82+
metadata['entries'] = entries
83+
self.cache.set(metadata_key, metadata)
7884

7985
return content
8086

87+
def get_metadata(self, file_path: Path):
88+
"""Get metadata for a file (for testing purposes)."""
89+
metadata_key = self._get_metadata_key(file_path)
90+
metadata = self.cache.get(metadata_key, {'entries': [], 'counter': 0})
91+
return metadata # Return the actual metadata, not a copy
92+
8193
def clear_history(self, file_path: Path):
8294
"""Clear history for a given file."""
8395
metadata_key = self._get_metadata_key(file_path)
@@ -105,8 +117,3 @@ def get_all_history(self, file_path: Path) -> List[str]:
105117
history.append(content)
106118

107119
return history
108-
109-
def get_metadata(self, file_path: Path):
110-
"""Get metadata for a file (for testing purposes)."""
111-
metadata_key = self._get_metadata_key(file_path)
112-
return self.cache.get(metadata_key, {'entries': [], 'counter': 0})

poetry.lock

Lines changed: 13 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "openhands-aci"
3-
version = "0.2.4"
3+
version = "0.2.5"
44
description = "An Agent-Computer Interface (ACI) designed for software development agents OpenHands."
55
authors = ["OpenHands"]
66
license = "MIT"
@@ -26,8 +26,9 @@ grep-ast = "0.3.3"
2626
flake8 = "*"
2727
whatthepatch = "^1.0.6"
2828
binaryornot = "^0.4.4"
29-
pytest = "^8.3.4"
30-
pre-commit = "^4.1.0"
29+
30+
31+
3132

3233

3334
[tool.poetry.group.dev.dependencies]

tests/integration/editor/test_peak_memory.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -241,22 +241,23 @@ def test_large_history_insert():
241241
large_content = 'x' * (1024 * 1024)
242242

243243
# Try to insert the large content multiple times
244-
for i in range(100):
244+
num_files = 100
245+
for i in range(num_files):
245246
try:
246247
manager.add_history(Path(f'test_file_{i}.txt'), large_content)
247248
except Exception as e:
248249
pytest.fail(f'Error occurred on iteration {i}: {str(e)}')
249250

250251
# Check if we can still retrieve the last entry
251-
last_content = manager.get_last_history(Path('test_file_99.txt'))
252+
last_content = manager.pop_last_history(Path(f'test_file_{num_files - 1}.txt'))
252253
assert (
253254
last_content == large_content
254255
), 'Failed to retrieve the last inserted content'
255256

256257
# Check if the number of cache entries is correct
257258
cache_entries = list(manager.cache)
258259
assert (
259-
len(cache_entries) == 200
260-
), f'Expected 200 cache entries (100 content + 100 metadata), but found {len(cache_entries)}'
261-
262-
print('Large history insert test completed successfully')
260+
len(cache_entries)
261+
== num_files * 2
262+
- 1 # The cache entry for file content was removed, only metadata remains
263+
), f'Expected {num_files * 2 - 1} cache entries ({num_files - 1} content + {num_files} metadata), but found {len(cache_entries)}'

tests/integration/test_oh_editor.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,35 @@ def test_undo_edit(editor):
356356
assert 'test file' in test_file.read_text() # Original content restored
357357

358358

359+
def test_multiple_undo_edits(editor):
360+
editor, test_file = editor
361+
# Make an edit to be undone
362+
_ = editor(
363+
command='str_replace',
364+
path=str(test_file),
365+
old_str='test file',
366+
new_str='sample file v1',
367+
)
368+
# Make another edit to be undone
369+
_ = editor(
370+
command='str_replace',
371+
path=str(test_file),
372+
old_str='sample file v1',
373+
new_str='sample file v2',
374+
)
375+
# Undo the last edit
376+
result = editor(command='undo_edit', path=str(test_file))
377+
assert isinstance(result, CLIResult)
378+
assert 'Last edit to' in result.output
379+
assert 'sample file v1' in test_file.read_text() # Previous content restored
380+
381+
# Undo the first edit
382+
result = editor(command='undo_edit', path=str(test_file))
383+
assert isinstance(result, CLIResult)
384+
assert 'Last edit to' in result.output
385+
assert 'test file' in test_file.read_text() # Original content restored
386+
387+
359388
def test_validate_path_invalid(editor):
360389
editor, test_file = editor
361390
invalid_file = test_file.parent / 'nonexistent.txt'

tests/unit/test_history.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,43 @@ def test_clear_history_resets_counter():
100100
metadata = manager.get_metadata(path)
101101
assert len(metadata['entries']) == 1
102102
assert metadata['entries'][0] == 0 # First key should be 0
103+
104+
105+
def test_pop_last_history_removes_entry():
106+
"""Test that pop_last_history removes the latest entry."""
107+
with tempfile.NamedTemporaryFile() as temp_file:
108+
path = Path(temp_file.name)
109+
manager = FileHistoryManager()
110+
111+
# Add some entries
112+
manager.add_history(path, 'content1')
113+
manager.add_history(path, 'content2')
114+
manager.add_history(path, 'content3')
115+
116+
# Pop the last history entry
117+
last_entry = manager.pop_last_history(path)
118+
assert last_entry == 'content3'
119+
120+
# Check that the entry has been removed
121+
metadata = manager.get_metadata(path)
122+
assert len(metadata['entries']) == 2
123+
124+
# Pop the last history entry again
125+
last_entry = manager.pop_last_history(path)
126+
assert last_entry == 'content2'
127+
128+
# Check that the entry has been removed
129+
metadata = manager.get_metadata(path)
130+
assert len(metadata['entries']) == 1
131+
132+
# Pop the last history entry one more time
133+
last_entry = manager.pop_last_history(path)
134+
assert last_entry == 'content1'
135+
136+
# Check that all entries have been removed
137+
metadata = manager.get_metadata(path)
138+
assert len(metadata['entries']) == 0
139+
140+
# Try to pop last history when there are no entries
141+
last_entry = manager.pop_last_history(path)
142+
assert last_entry is None

0 commit comments

Comments
 (0)