fix(dream): filter non-Dream history commits (#4905)
* fix(dream): filter non-Dream history commits * fix(dream): resolve filtered commit diffs
This commit is contained in:
+35
-18
@@ -560,6 +560,9 @@ def _format_changed_files(diff: str) -> str:
|
||||
return ", ".join(f"`{path}`" for path in files)
|
||||
|
||||
|
||||
_DREAM_COMMIT_PREFIX = "dream:"
|
||||
|
||||
|
||||
def _format_dream_log_content(commit, diff: str, *, requested_sha: str | None = None) -> str:
|
||||
files_line = _format_changed_files(diff)
|
||||
lines = [
|
||||
@@ -608,7 +611,7 @@ def _format_dream_restore_list(commits: list) -> str:
|
||||
async def cmd_dream_log(ctx: CommandContext) -> OutboundMessage:
|
||||
"""Show what the last Dream changed.
|
||||
|
||||
Default: diff of the latest commit (HEAD~1 vs HEAD).
|
||||
Default: diff of the latest Dream commit versus its parent.
|
||||
With /dream-log <sha>: diff of that specific commit.
|
||||
"""
|
||||
store = ctx.loop.consolidator.store
|
||||
@@ -643,9 +646,16 @@ async def cmd_dream_log(ctx: CommandContext) -> OutboundMessage:
|
||||
commit, diff = result
|
||||
content = _format_dream_log_content(commit, diff, requested_sha=sha)
|
||||
else:
|
||||
# Default: show the latest commit's diff
|
||||
commits = git.log(max_entries=1)
|
||||
result = git.show_commit_diff(commits[0].sha) if commits else None
|
||||
# Default: show the latest Dream commit's diff
|
||||
commits = git.log(max_entries=1, message_prefix=_DREAM_COMMIT_PREFIX)
|
||||
result = (
|
||||
git.show_commit_diff(
|
||||
commits[0].sha,
|
||||
max_entries=1,
|
||||
message_prefix=_DREAM_COMMIT_PREFIX,
|
||||
)
|
||||
if commits else None
|
||||
)
|
||||
if result:
|
||||
commit, diff = result
|
||||
content = _format_dream_log_content(commit, diff)
|
||||
@@ -678,29 +688,36 @@ async def cmd_dream_restore(ctx: CommandContext) -> OutboundMessage:
|
||||
|
||||
args = ctx.args.strip()
|
||||
if not args:
|
||||
# Show recent commits for the user to pick
|
||||
commits = git.log(max_entries=10)
|
||||
# Show recent Dream commits for the user to pick
|
||||
commits = git.log(max_entries=10, message_prefix=_DREAM_COMMIT_PREFIX)
|
||||
if not commits:
|
||||
content = "Dream memory has no saved versions to restore yet."
|
||||
else:
|
||||
content = _format_dream_restore_list(commits)
|
||||
else:
|
||||
sha = args.split()[0]
|
||||
result = git.show_commit_diff(sha)
|
||||
changed_files = _format_changed_files(result[1]) if result else "the tracked memory files"
|
||||
new_sha = git.revert(sha)
|
||||
if new_sha:
|
||||
content = (
|
||||
f"Restored Dream memory to the state before `{sha}`.\n\n"
|
||||
f"- New safety commit: `{new_sha}`\n"
|
||||
f"- Restored files: {changed_files}\n\n"
|
||||
f"Use `/dream-log {new_sha}` to inspect the restore diff."
|
||||
)
|
||||
else:
|
||||
result = git.show_commit_diff(sha, message_prefix=_DREAM_COMMIT_PREFIX)
|
||||
if not result:
|
||||
content = (
|
||||
f"Couldn't restore Dream change `{sha}`.\n\n"
|
||||
"It may not exist, or it may be the first saved version with no earlier state to restore."
|
||||
"Only Dream memory versions can be restored. "
|
||||
"Use `/dream-restore` to list recent versions."
|
||||
)
|
||||
else:
|
||||
changed_files = _format_changed_files(result[1])
|
||||
new_sha = git.revert(sha, message_prefix=_DREAM_COMMIT_PREFIX)
|
||||
if new_sha:
|
||||
content = (
|
||||
f"Restored Dream memory to the state before `{sha}`.\n\n"
|
||||
f"- New safety commit: `{new_sha}`\n"
|
||||
f"- Restored files: {changed_files}\n\n"
|
||||
f"Use `/dream-log {new_sha}` to inspect the restore diff."
|
||||
)
|
||||
else:
|
||||
content = (
|
||||
f"Couldn't restore Dream change `{sha}`.\n\n"
|
||||
"It may be the first saved version with no earlier state to restore."
|
||||
)
|
||||
return OutboundMessage(
|
||||
channel=ctx.msg.channel, chat_id=ctx.msg.chat_id,
|
||||
content=content, metadata={"render_as": "text"},
|
||||
|
||||
+54
-20
@@ -214,8 +214,16 @@ class GitStore:
|
||||
|
||||
# -- query -----------------------------------------------------------------
|
||||
|
||||
def log(self, max_entries: int = 20) -> list[CommitInfo]:
|
||||
"""Return simplified commit log."""
|
||||
def log(
|
||||
self,
|
||||
max_entries: int = 20,
|
||||
message_prefix: str | None = None,
|
||||
) -> list[CommitInfo]:
|
||||
"""Return simplified commit log, optionally filtered by message prefix.
|
||||
|
||||
When filtering, *max_entries* counts matching commits rather than every
|
||||
commit traversed in the repository.
|
||||
"""
|
||||
if not self.is_initialized():
|
||||
return []
|
||||
|
||||
@@ -239,11 +247,12 @@ class GitStore:
|
||||
time.localtime(commit.commit_time),
|
||||
)
|
||||
msg = commit.message.decode("utf-8", errors="replace").strip()
|
||||
entries.append(CommitInfo(
|
||||
sha=sha.hex()[:8],
|
||||
message=msg,
|
||||
timestamp=ts,
|
||||
))
|
||||
if message_prefix is None or msg.startswith(message_prefix):
|
||||
entries.append(CommitInfo(
|
||||
sha=sha.hex()[:8],
|
||||
message=msg,
|
||||
timestamp=ts,
|
||||
))
|
||||
sha = commit.parents[0] if commit.parents else None
|
||||
|
||||
return entries
|
||||
@@ -424,25 +433,41 @@ class GitStore:
|
||||
return c
|
||||
return None
|
||||
|
||||
def show_commit_diff(self, short_sha: str, max_entries: int = 20) -> tuple[CommitInfo, str] | None:
|
||||
"""Find a commit and return it with its diff vs the parent."""
|
||||
commits = self.log(max_entries=max_entries)
|
||||
for i, c in enumerate(commits):
|
||||
if c.sha.startswith(short_sha):
|
||||
if i + 1 < len(commits):
|
||||
diff = self.diff_commits(commits[i + 1].sha, c.sha)
|
||||
else:
|
||||
diff = ""
|
||||
return c, diff
|
||||
return None
|
||||
def show_commit_diff(
|
||||
self,
|
||||
short_sha: str,
|
||||
max_entries: int = 20,
|
||||
message_prefix: str | None = None,
|
||||
) -> tuple[CommitInfo, str] | None:
|
||||
"""Find a commit and return it with its diff vs its actual parent."""
|
||||
try:
|
||||
from dulwich.repo import Repo
|
||||
|
||||
commits = self.log(max_entries=max_entries, message_prefix=message_prefix)
|
||||
for c in commits:
|
||||
if c.sha.startswith(short_sha):
|
||||
full_sha = self._resolve_sha(c.sha)
|
||||
if not full_sha:
|
||||
return None
|
||||
with Repo(str(self._workspace)) as repo:
|
||||
commit = repo[full_sha]
|
||||
parent = commit.parents[0] if commit.parents else None
|
||||
diff = self.diff_commits(parent.hex()[:8], c.sha) if parent else ""
|
||||
return c, diff
|
||||
return None
|
||||
except Exception:
|
||||
logger.exception("Git show_commit_diff failed")
|
||||
return None
|
||||
|
||||
# -- restore ---------------------------------------------------------------
|
||||
|
||||
def revert(self, commit: str) -> str | None:
|
||||
def revert(self, commit: str, *, message_prefix: str | None = None) -> str | None:
|
||||
"""Revert (undo) the changes introduced by the given commit.
|
||||
|
||||
Restores all tracked memory files to the state at the commit's parent,
|
||||
then creates a new commit recording the revert.
|
||||
then creates a new commit recording the revert. When *message_prefix*
|
||||
is provided, commits outside that history are rejected before any files
|
||||
are changed.
|
||||
|
||||
Returns the new commit SHA, or None on failure.
|
||||
"""
|
||||
@@ -462,6 +487,15 @@ class GitStore:
|
||||
if commit_obj.type_name != b"commit":
|
||||
return None
|
||||
|
||||
commit_message = commit_obj.message.decode("utf-8", errors="replace").strip()
|
||||
if message_prefix is not None and not commit_message.startswith(message_prefix):
|
||||
logger.warning(
|
||||
"Git revert: commit {} does not match message prefix {!r}",
|
||||
commit,
|
||||
message_prefix,
|
||||
)
|
||||
return None
|
||||
|
||||
if not commit_obj.parents:
|
||||
logger.warning("Git revert: cannot revert root commit {}", commit)
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user