trailofbits / trailofbits/graphtage
Document how to consume a diff programmatically
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 2.5k
- Forks
- 61
- Avg merge
- 6h 45m
- Merged PRs (30d)
- 45
Description
docs/library.rst shows how to build trees, diff them, and print the result, but it stops before the case where you want to keep the diff instead of printing it. Splitting this out of #75, where the reporter wanted to feed two document trees to Graphtage and get back a third tree whose changed subtrees are wrapped in their own Diff(before, after) node, and had to reverse-engineer the API to do it.
Everything needed is already public. graphtage.pydiff.diff returns an edited tree, and every node in it is a graphtage.EditedTreeNode carrying removed, inserted, matched_to, edit, and edit_list (graphtage/tree.py:283-323). Those attributes are populated by Match.on_diff, Remove.on_diff, and Insert.on_diff (graphtage/edits.py:310, :373, :406). What is missing is a page that puts them together:
from graphtage.pydiff import diff
def summarize(node, depth=0):
for child in node.children():
if child.removed:
print(" " * depth, "removed:", child.to_obj())
elif child.matched_to is not None and child.edit.has_non_zero_cost():
print(" " * depth, "changed:", child.to_obj(), "->", child.matched_to.to_obj())
for inserted in child.inserted:
print(" " * depth, "inserted:", inserted.to_obj())
summarize(child, depth + 1)
summarize(diff({"a": 1, "b": [1, 3]}, {"a": 2, "b": [1, 2, 3]}))
changed: 1 -> 2
inserted: 2
Points that are not obvious from the current documentation and that the page should state:
Insert.on_diffannotates the container, not the inserted node, so you read insertions from a parent'sinsertedlist rather than from a flag on the new child.- A changed scalar comes back as a
Matchwith a non-zero cost, not as aReplace, so testingisinstance(edit, Replace)misses most value changes. Usehas_non_zero_cost(). TreeNode.get_all_edit_contexts(graphtage/tree.py:558) yields(ancestor_path, edit)pairs and is the better starting point when you need to know where an edit happened.__main__.py:389uses it to implement--edit-digest.to_obj()converts a node back to plain Python, but its docstring currently scopes it to--match-ifexpression evaluation. Either widen that or point readers atTreeNode.copy()andcopy_from()instead.
Suggested home: a new section in docs/library.rst after "Diffing In-Memory Python Objects".
Filed to track the part of #75 that remains after the UTF-8 decoding error reported there was fixed in v0.2.8. cc @SamWilsn
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the "Diffing In-Memory Python Objects" section in docs/library.rst, then read graphtage.pydiff.diff and the EditedTreeNode attributes in graphtage/tree.py. Review TreeNode.get_all_edit_contexts and its use in main.py, and use the supplied example as the check. Done means the new section explains collecting changed, removed, and inserted nodes, edit contexts, and converting nodes to plain Python.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- documentation
- Issue type
- Documentation
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 90/100