fix(images): don't overwrite relative images that share a basename - #122

Closed
jichaowang02-lang wants to merge 2 commits into
VectifyAI:mainfrom
jichaowang02-lang:fix/relative-image-basename-collision
Closed

fix(images): don't overwrite relative images that share a basename#122
jichaowang02-lang wants to merge 2 commits into
VectifyAI:mainfrom
jichaowang02-lang:fix/relative-image-basename-collision

Conversation

@jichaowang02-lang

Copy link
Copy Markdown
Contributor

Summary

copy_relative_images could silently lose an image and render the wrong
one. It named every destination after src.name (the basename only), so two
references to different files that happen to share a basename collide.

Root cause

filename=src.name# basename onlydest=images_dir/filenameshutil.copy2(src, dest) # second same-basename source overwrites the first

For ![a](a/logo.png) + ![b](b/logo.png):

  • both copy to images_dir/logo.png → the second copy2 overwrites the first
    (image A's bytes are gone), and
  • both links are rewritten to sources/images/<doc>/logo.png, so the rendered
    document shows the same image in both places.

extract_base64_images already avoids this by numbering destinations
(img_{counter:03d}); only the relative-image path was affected.

inputbeforeafter
a/logo.png + b/logo.png (distinct)1 file, both links → it, A lost2 files (logo.png, logo_1.png), distinct links
logo.png referenced twice (same file)1 file1 file (deduped, both links agree)

Fix

Track the destination assigned to each source: reuse it when the identical
source is referenced more than once (no duplicate copy), and disambiguate with
a {stem}_{n}{suffix} suffix when a different source would collide on a
name already taken.

Testing

$ pytest tests/test_images.py -q
12 passed
$ ruff check openkb/images.py tests/test_images.py
All checks passed!

Adds test_same_basename_different_dirs_no_overwrite (both images preserved,
links distinct) and test_same_image_referenced_twice_is_copied_once (dedup).

`copy_relative_images` named each destination after `src.name` (the basename
only). Two image references to different files that share a basename — e.g.
`![a](a/logo.png)` and `![b](b/logo.png)` — both copied to
`images_dir/logo.png`, so the second `shutil.copy2` silently overwrote the
first (one image's bytes are lost) and both links were rewritten to the same
path, rendering the same image in both places.
Track an assigned destination per source: reuse it when the identical source
is referenced more than once (no duplicate copy), and disambiguate with a
`{stem}_{n}{suffix}` suffix when a different source would collide on a name
already taken. (`extract_base64_images` already avoids this via its
`img_{counter:03d}` scheme.)
Adds regression tests for the same-basename collision and the
identical-source-referenced-twice cases.
CopilotAI review requested due to automatic review settings June 20, 2026 03:16

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a data-loss bug in the Markdown conversion pipeline where copy_relative_images could overwrite one relative image with another when different source paths share the same basename (e.g., a/logo.png and b/logo.png), causing both links to render the same copied file.

Changes:

  • Add per-source destination tracking to dedupe repeated references to the same image and disambiguate basename collisions via {stem}_{n}{suffix}.
  • Add tests covering same-basename/different-source behavior and same-source referenced twice (copy once).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

FileDescription
openkb/images.pyPrevent basename collisions and dedupe repeated references when copying relative images.
tests/test_images.pyAdd regression tests for basename collisions and deduplication behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadopenkb/images.py Outdated
Comment on lines +231 to +232
assigned: dict[Path, str] = {}
taken: set[str] = set()
…g prior files
Review feedback: copy_relative_images relied on the in-call taken set to
disambiguate basenames, but a file already present in images_dir (e.g. from a
prior conversion) was not in taken, so a same-basename source could still
overwrite it. Seed taken from the existing directory contents.

@KylinMountainKylinMountain left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Core fix (first commit) is correct.

The second commit — seeding taken from images_dir, per Copilot's note — is the problem. This dir is per-doc_name, so it only ever holds this doc's own images from a prior run; overwriting them on re-convert is the desired refresh, not data loss. The real bug (two different sources colliding in one pass) is already prevented by taken starting empty. Seeding from disk instead makes re-convert non-idempotent — stale orphans + _1/_2 link churn on every edit.

Suggest dropping the second commit — the first one alone is the right fix.

KylinMountain added a commit that referenced this pull request Jul 2, 2026
Two relative source images with the same basename but different paths
(e.g. a/logo.png and b/logo.png) overwrote each other in
sources/images/<doc>/, collapsing both markdown links onto one file.
Track the destination assigned to each source (so an image referenced
twice is copied once) and suffix genuine basename collisions
(logo.png -> logo_1.png).
Adapted from #122 by @jichaowang02-lang; drops that PR's second commit,
which seeded the taken-name set from the existing images_dir and thereby
broke re-convert idempotency (a changed same-basename image got a fresh
suffix each run, orphaning the old file and churning links).
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
Co-authored-by: jichao wang <jichaowang02@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@jichaowang02-lang@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(images): don't overwrite relative images that share a basename - #122

Closed
jichaowang02-lang wants to merge 2 commits into
VectifyAI:mainfrom
jichaowang02-lang:fix/relative-image-basename-collision
Closed

fix(images): don't overwrite relative images that share a basename#122
jichaowang02-lang wants to merge 2 commits into
VectifyAI:mainfrom
jichaowang02-lang:fix/relative-image-basename-collision

Conversation

@jichaowang02-lang

Copy link
Copy Markdown
Contributor

Summary

copy_relative_images could silently lose an image and render the wrong
one. It named every destination after src.name (the basename only), so two
references to different files that happen to share a basename collide.

Root cause

filename=src.name# basename onlydest=images_dir/filenameshutil.copy2(src, dest) # second same-basename source overwrites the first

For ![a](a/logo.png) + ![b](b/logo.png):

  • both copy to images_dir/logo.png → the second copy2 overwrites the first
    (image A's bytes are gone), and
  • both links are rewritten to sources/images/<doc>/logo.png, so the rendered
    document shows the same image in both places.

extract_base64_images already avoids this by numbering destinations
(img_{counter:03d}); only the relative-image path was affected.

inputbeforeafter
a/logo.png + b/logo.png (distinct)1 file, both links → it, A lost2 files (logo.png, logo_1.png), distinct links
logo.png referenced twice (same file)1 file1 file (deduped, both links agree)

Fix

Track the destination assigned to each source: reuse it when the identical
source is referenced more than once (no duplicate copy), and disambiguate with
a {stem}_{n}{suffix} suffix when a different source would collide on a
name already taken.

Testing

$ pytest tests/test_images.py -q
12 passed
$ ruff check openkb/images.py tests/test_images.py
All checks passed!

Adds test_same_basename_different_dirs_no_overwrite (both images preserved,
links distinct) and test_same_image_referenced_twice_is_copied_once (dedup).

`copy_relative_images` named each destination after `src.name` (the basename
only). Two image references to different files that share a basename — e.g.
`![a](a/logo.png)` and `![b](b/logo.png)` — both copied to
`images_dir/logo.png`, so the second `shutil.copy2` silently overwrote the
first (one image's bytes are lost) and both links were rewritten to the same
path, rendering the same image in both places.
Track an assigned destination per source: reuse it when the identical source
is referenced more than once (no duplicate copy), and disambiguate with a
`{stem}_{n}{suffix}` suffix when a different source would collide on a name
already taken. (`extract_base64_images` already avoids this via its
`img_{counter:03d}` scheme.)
Adds regression tests for the same-basename collision and the
identical-source-referenced-twice cases.
CopilotAI review requested due to automatic review settings June 20, 2026 03:16

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a data-loss bug in the Markdown conversion pipeline where copy_relative_images could overwrite one relative image with another when different source paths share the same basename (e.g., a/logo.png and b/logo.png), causing both links to render the same copied file.

Changes:

  • Add per-source destination tracking to dedupe repeated references to the same image and disambiguate basename collisions via {stem}_{n}{suffix}.
  • Add tests covering same-basename/different-source behavior and same-source referenced twice (copy once).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

FileDescription
openkb/images.pyPrevent basename collisions and dedupe repeated references when copying relative images.
tests/test_images.pyAdd regression tests for basename collisions and deduplication behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadopenkb/images.py Outdated
Comment on lines +231 to +232
assigned: dict[Path, str] = {}
taken: set[str] = set()
…g prior files
Review feedback: copy_relative_images relied on the in-call taken set to
disambiguate basenames, but a file already present in images_dir (e.g. from a
prior conversion) was not in taken, so a same-basename source could still
overwrite it. Seed taken from the existing directory contents.

@KylinMountainKylinMountain left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Core fix (first commit) is correct.

The second commit — seeding taken from images_dir, per Copilot's note — is the problem. This dir is per-doc_name, so it only ever holds this doc's own images from a prior run; overwriting them on re-convert is the desired refresh, not data loss. The real bug (two different sources colliding in one pass) is already prevented by taken starting empty. Seeding from disk instead makes re-convert non-idempotent — stale orphans + _1/_2 link churn on every edit.

Suggest dropping the second commit — the first one alone is the right fix.

KylinMountain added a commit that referenced this pull request Jul 2, 2026
Two relative source images with the same basename but different paths
(e.g. a/logo.png and b/logo.png) overwrote each other in
sources/images/<doc>/, collapsing both markdown links onto one file.
Track the destination assigned to each source (so an image referenced
twice is copied once) and suffix genuine basename collisions
(logo.png -> logo_1.png).
Adapted from #122 by @jichaowang02-lang; drops that PR's second commit,
which seeded the taken-name set from the existing images_dir and thereby
broke re-convert idempotency (a changed same-basename image got a fresh
suffix each run, orphaning the old file and churning links).
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
Co-authored-by: jichao wang <jichaowang02@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@jichaowang02-lang@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(images): don't overwrite relative images that share a basename - #122

Closed
jichaowang02-lang wants to merge 2 commits into
VectifyAI:mainfrom
jichaowang02-lang:fix/relative-image-basename-collision
Closed

fix(images): don't overwrite relative images that share a basename#122
jichaowang02-lang wants to merge 2 commits into
VectifyAI:mainfrom
jichaowang02-lang:fix/relative-image-basename-collision

Conversation

@jichaowang02-lang

Copy link
Copy Markdown
Contributor

Summary

copy_relative_images could silently lose an image and render the wrong
one. It named every destination after src.name (the basename only), so two
references to different files that happen to share a basename collide.

Root cause

filename=src.name# basename onlydest=images_dir/filenameshutil.copy2(src, dest) # second same-basename source overwrites the first

For ![a](a/logo.png) + ![b](b/logo.png):

  • both copy to images_dir/logo.png → the second copy2 overwrites the first
    (image A's bytes are gone), and
  • both links are rewritten to sources/images/<doc>/logo.png, so the rendered
    document shows the same image in both places.

extract_base64_images already avoids this by numbering destinations
(img_{counter:03d}); only the relative-image path was affected.

inputbeforeafter
a/logo.png + b/logo.png (distinct)1 file, both links → it, A lost2 files (logo.png, logo_1.png), distinct links
logo.png referenced twice (same file)1 file1 file (deduped, both links agree)

Fix

Track the destination assigned to each source: reuse it when the identical
source is referenced more than once (no duplicate copy), and disambiguate with
a {stem}_{n}{suffix} suffix when a different source would collide on a
name already taken.

Testing

$ pytest tests/test_images.py -q
12 passed
$ ruff check openkb/images.py tests/test_images.py
All checks passed!

Adds test_same_basename_different_dirs_no_overwrite (both images preserved,
links distinct) and test_same_image_referenced_twice_is_copied_once (dedup).

`copy_relative_images` named each destination after `src.name` (the basename
only). Two image references to different files that share a basename — e.g.
`![a](a/logo.png)` and `![b](b/logo.png)` — both copied to
`images_dir/logo.png`, so the second `shutil.copy2` silently overwrote the
first (one image's bytes are lost) and both links were rewritten to the same
path, rendering the same image in both places.
Track an assigned destination per source: reuse it when the identical source
is referenced more than once (no duplicate copy), and disambiguate with a
`{stem}_{n}{suffix}` suffix when a different source would collide on a name
already taken. (`extract_base64_images` already avoids this via its
`img_{counter:03d}` scheme.)
Adds regression tests for the same-basename collision and the
identical-source-referenced-twice cases.
CopilotAI review requested due to automatic review settings June 20, 2026 03:16

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a data-loss bug in the Markdown conversion pipeline where copy_relative_images could overwrite one relative image with another when different source paths share the same basename (e.g., a/logo.png and b/logo.png), causing both links to render the same copied file.

Changes:

  • Add per-source destination tracking to dedupe repeated references to the same image and disambiguate basename collisions via {stem}_{n}{suffix}.
  • Add tests covering same-basename/different-source behavior and same-source referenced twice (copy once).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

FileDescription
openkb/images.pyPrevent basename collisions and dedupe repeated references when copying relative images.
tests/test_images.pyAdd regression tests for basename collisions and deduplication behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadopenkb/images.py Outdated
Comment on lines +231 to +232
assigned: dict[Path, str] = {}
taken: set[str] = set()
…g prior files
Review feedback: copy_relative_images relied on the in-call taken set to
disambiguate basenames, but a file already present in images_dir (e.g. from a
prior conversion) was not in taken, so a same-basename source could still
overwrite it. Seed taken from the existing directory contents.

@KylinMountainKylinMountain left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Core fix (first commit) is correct.

The second commit — seeding taken from images_dir, per Copilot's note — is the problem. This dir is per-doc_name, so it only ever holds this doc's own images from a prior run; overwriting them on re-convert is the desired refresh, not data loss. The real bug (two different sources colliding in one pass) is already prevented by taken starting empty. Seeding from disk instead makes re-convert non-idempotent — stale orphans + _1/_2 link churn on every edit.

Suggest dropping the second commit — the first one alone is the right fix.

KylinMountain added a commit that referenced this pull request Jul 2, 2026
Two relative source images with the same basename but different paths
(e.g. a/logo.png and b/logo.png) overwrote each other in
sources/images/<doc>/, collapsing both markdown links onto one file.
Track the destination assigned to each source (so an image referenced
twice is copied once) and suffix genuine basename collisions
(logo.png -> logo_1.png).
Adapted from #122 by @jichaowang02-lang; drops that PR's second commit,
which seeded the taken-name set from the existing images_dir and thereby
broke re-convert idempotency (a changed same-basename image got a fresh
suffix each run, orphaning the old file and churning links).
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
Co-authored-by: jichao wang <jichaowang02@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@jichaowang02-lang@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(images): don't overwrite relative images that share a basename - #122

Closed
jichaowang02-lang wants to merge 2 commits into
VectifyAI:mainfrom
jichaowang02-lang:fix/relative-image-basename-collision
Closed

fix(images): don't overwrite relative images that share a basename#122
jichaowang02-lang wants to merge 2 commits into
VectifyAI:mainfrom
jichaowang02-lang:fix/relative-image-basename-collision

Conversation

@jichaowang02-lang

Copy link
Copy Markdown
Contributor

Summary

copy_relative_images could silently lose an image and render the wrong
one. It named every destination after src.name (the basename only), so two
references to different files that happen to share a basename collide.

Root cause

filename=src.name# basename onlydest=images_dir/filenameshutil.copy2(src, dest) # second same-basename source overwrites the first

For ![a](a/logo.png) + ![b](b/logo.png):

  • both copy to images_dir/logo.png → the second copy2 overwrites the first
    (image A's bytes are gone), and
  • both links are rewritten to sources/images/<doc>/logo.png, so the rendered
    document shows the same image in both places.

extract_base64_images already avoids this by numbering destinations
(img_{counter:03d}); only the relative-image path was affected.

inputbeforeafter
a/logo.png + b/logo.png (distinct)1 file, both links → it, A lost2 files (logo.png, logo_1.png), distinct links
logo.png referenced twice (same file)1 file1 file (deduped, both links agree)

Fix

Track the destination assigned to each source: reuse it when the identical
source is referenced more than once (no duplicate copy), and disambiguate with
a {stem}_{n}{suffix} suffix when a different source would collide on a
name already taken.

Testing

$ pytest tests/test_images.py -q
12 passed
$ ruff check openkb/images.py tests/test_images.py
All checks passed!

Adds test_same_basename_different_dirs_no_overwrite (both images preserved,
links distinct) and test_same_image_referenced_twice_is_copied_once (dedup).

`copy_relative_images` named each destination after `src.name` (the basename
only). Two image references to different files that share a basename — e.g.
`![a](a/logo.png)` and `![b](b/logo.png)` — both copied to
`images_dir/logo.png`, so the second `shutil.copy2` silently overwrote the
first (one image's bytes are lost) and both links were rewritten to the same
path, rendering the same image in both places.
Track an assigned destination per source: reuse it when the identical source
is referenced more than once (no duplicate copy), and disambiguate with a
`{stem}_{n}{suffix}` suffix when a different source would collide on a name
already taken. (`extract_base64_images` already avoids this via its
`img_{counter:03d}` scheme.)
Adds regression tests for the same-basename collision and the
identical-source-referenced-twice cases.
CopilotAI review requested due to automatic review settings June 20, 2026 03:16

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a data-loss bug in the Markdown conversion pipeline where copy_relative_images could overwrite one relative image with another when different source paths share the same basename (e.g., a/logo.png and b/logo.png), causing both links to render the same copied file.

Changes:

  • Add per-source destination tracking to dedupe repeated references to the same image and disambiguate basename collisions via {stem}_{n}{suffix}.
  • Add tests covering same-basename/different-source behavior and same-source referenced twice (copy once).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

FileDescription
openkb/images.pyPrevent basename collisions and dedupe repeated references when copying relative images.
tests/test_images.pyAdd regression tests for basename collisions and deduplication behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadopenkb/images.py Outdated
Comment on lines +231 to +232
assigned: dict[Path, str] = {}
taken: set[str] = set()
…g prior files
Review feedback: copy_relative_images relied on the in-call taken set to
disambiguate basenames, but a file already present in images_dir (e.g. from a
prior conversion) was not in taken, so a same-basename source could still
overwrite it. Seed taken from the existing directory contents.

@KylinMountainKylinMountain left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Core fix (first commit) is correct.

The second commit — seeding taken from images_dir, per Copilot's note — is the problem. This dir is per-doc_name, so it only ever holds this doc's own images from a prior run; overwriting them on re-convert is the desired refresh, not data loss. The real bug (two different sources colliding in one pass) is already prevented by taken starting empty. Seeding from disk instead makes re-convert non-idempotent — stale orphans + _1/_2 link churn on every edit.

Suggest dropping the second commit — the first one alone is the right fix.

KylinMountain added a commit that referenced this pull request Jul 2, 2026
Two relative source images with the same basename but different paths
(e.g. a/logo.png and b/logo.png) overwrote each other in
sources/images/<doc>/, collapsing both markdown links onto one file.
Track the destination assigned to each source (so an image referenced
twice is copied once) and suffix genuine basename collisions
(logo.png -> logo_1.png).
Adapted from #122 by @jichaowang02-lang; drops that PR's second commit,
which seeded the taken-name set from the existing images_dir and thereby
broke re-convert idempotency (a changed same-basename image got a fresh
suffix each run, orphaning the old file and churning links).
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
Co-authored-by: jichao wang <jichaowang02@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@jichaowang02-lang@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(images): don't overwrite relative images that share a basename - #122

Closed
jichaowang02-lang wants to merge 2 commits into
VectifyAI:mainfrom
jichaowang02-lang:fix/relative-image-basename-collision
Closed

fix(images): don't overwrite relative images that share a basename#122
jichaowang02-lang wants to merge 2 commits into
VectifyAI:mainfrom
jichaowang02-lang:fix/relative-image-basename-collision

Conversation

@jichaowang02-lang

Copy link
Copy Markdown
Contributor

Summary

copy_relative_images could silently lose an image and render the wrong
one. It named every destination after src.name (the basename only), so two
references to different files that happen to share a basename collide.

Root cause

filename=src.name# basename onlydest=images_dir/filenameshutil.copy2(src, dest) # second same-basename source overwrites the first

For ![a](a/logo.png) + ![b](b/logo.png):

  • both copy to images_dir/logo.png → the second copy2 overwrites the first
    (image A's bytes are gone), and
  • both links are rewritten to sources/images/<doc>/logo.png, so the rendered
    document shows the same image in both places.

extract_base64_images already avoids this by numbering destinations
(img_{counter:03d}); only the relative-image path was affected.

inputbeforeafter
a/logo.png + b/logo.png (distinct)1 file, both links → it, A lost2 files (logo.png, logo_1.png), distinct links
logo.png referenced twice (same file)1 file1 file (deduped, both links agree)

Fix

Track the destination assigned to each source: reuse it when the identical
source is referenced more than once (no duplicate copy), and disambiguate with
a {stem}_{n}{suffix} suffix when a different source would collide on a
name already taken.

Testing

$ pytest tests/test_images.py -q
12 passed
$ ruff check openkb/images.py tests/test_images.py
All checks passed!

Adds test_same_basename_different_dirs_no_overwrite (both images preserved,
links distinct) and test_same_image_referenced_twice_is_copied_once (dedup).

`copy_relative_images` named each destination after `src.name` (the basename
only). Two image references to different files that share a basename — e.g.
`![a](a/logo.png)` and `![b](b/logo.png)` — both copied to
`images_dir/logo.png`, so the second `shutil.copy2` silently overwrote the
first (one image's bytes are lost) and both links were rewritten to the same
path, rendering the same image in both places.
Track an assigned destination per source: reuse it when the identical source
is referenced more than once (no duplicate copy), and disambiguate with a
`{stem}_{n}{suffix}` suffix when a different source would collide on a name
already taken. (`extract_base64_images` already avoids this via its
`img_{counter:03d}` scheme.)
Adds regression tests for the same-basename collision and the
identical-source-referenced-twice cases.
CopilotAI review requested due to automatic review settings June 20, 2026 03:16

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a data-loss bug in the Markdown conversion pipeline where copy_relative_images could overwrite one relative image with another when different source paths share the same basename (e.g., a/logo.png and b/logo.png), causing both links to render the same copied file.

Changes:

  • Add per-source destination tracking to dedupe repeated references to the same image and disambiguate basename collisions via {stem}_{n}{suffix}.
  • Add tests covering same-basename/different-source behavior and same-source referenced twice (copy once).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

FileDescription
openkb/images.pyPrevent basename collisions and dedupe repeated references when copying relative images.
tests/test_images.pyAdd regression tests for basename collisions and deduplication behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadopenkb/images.py Outdated
Comment on lines +231 to +232
assigned: dict[Path, str] = {}
taken: set[str] = set()
…g prior files
Review feedback: copy_relative_images relied on the in-call taken set to
disambiguate basenames, but a file already present in images_dir (e.g. from a
prior conversion) was not in taken, so a same-basename source could still
overwrite it. Seed taken from the existing directory contents.

@KylinMountainKylinMountain left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Core fix (first commit) is correct.

The second commit — seeding taken from images_dir, per Copilot's note — is the problem. This dir is per-doc_name, so it only ever holds this doc's own images from a prior run; overwriting them on re-convert is the desired refresh, not data loss. The real bug (two different sources colliding in one pass) is already prevented by taken starting empty. Seeding from disk instead makes re-convert non-idempotent — stale orphans + _1/_2 link churn on every edit.

Suggest dropping the second commit — the first one alone is the right fix.

KylinMountain added a commit that referenced this pull request Jul 2, 2026
Two relative source images with the same basename but different paths
(e.g. a/logo.png and b/logo.png) overwrote each other in
sources/images/<doc>/, collapsing both markdown links onto one file.
Track the destination assigned to each source (so an image referenced
twice is copied once) and suffix genuine basename collisions
(logo.png -> logo_1.png).
Adapted from #122 by @jichaowang02-lang; drops that PR's second commit,
which seeded the taken-name set from the existing images_dir and thereby
broke re-convert idempotency (a changed same-basename image got a fresh
suffix each run, orphaning the old file and churning links).
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
Co-authored-by: jichao wang <jichaowang02@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@jichaowang02-lang@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(images): don't overwrite relative images that share a basename - #122

Closed
jichaowang02-lang wants to merge 2 commits into
VectifyAI:mainfrom
jichaowang02-lang:fix/relative-image-basename-collision
Closed

fix(images): don't overwrite relative images that share a basename#122
jichaowang02-lang wants to merge 2 commits into
VectifyAI:mainfrom
jichaowang02-lang:fix/relative-image-basename-collision

Conversation

@jichaowang02-lang

Copy link
Copy Markdown
Contributor

Summary

copy_relative_images could silently lose an image and render the wrong
one. It named every destination after src.name (the basename only), so two
references to different files that happen to share a basename collide.

Root cause

filename=src.name# basename onlydest=images_dir/filenameshutil.copy2(src, dest) # second same-basename source overwrites the first

For ![a](a/logo.png) + ![b](b/logo.png):

  • both copy to images_dir/logo.png → the second copy2 overwrites the first
    (image A's bytes are gone), and
  • both links are rewritten to sources/images/<doc>/logo.png, so the rendered
    document shows the same image in both places.

extract_base64_images already avoids this by numbering destinations
(img_{counter:03d}); only the relative-image path was affected.

inputbeforeafter
a/logo.png + b/logo.png (distinct)1 file, both links → it, A lost2 files (logo.png, logo_1.png), distinct links
logo.png referenced twice (same file)1 file1 file (deduped, both links agree)

Fix

Track the destination assigned to each source: reuse it when the identical
source is referenced more than once (no duplicate copy), and disambiguate with
a {stem}_{n}{suffix} suffix when a different source would collide on a
name already taken.

Testing

$ pytest tests/test_images.py -q
12 passed
$ ruff check openkb/images.py tests/test_images.py
All checks passed!

Adds test_same_basename_different_dirs_no_overwrite (both images preserved,
links distinct) and test_same_image_referenced_twice_is_copied_once (dedup).

`copy_relative_images` named each destination after `src.name` (the basename
only). Two image references to different files that share a basename — e.g.
`![a](a/logo.png)` and `![b](b/logo.png)` — both copied to
`images_dir/logo.png`, so the second `shutil.copy2` silently overwrote the
first (one image's bytes are lost) and both links were rewritten to the same
path, rendering the same image in both places.
Track an assigned destination per source: reuse it when the identical source
is referenced more than once (no duplicate copy), and disambiguate with a
`{stem}_{n}{suffix}` suffix when a different source would collide on a name
already taken. (`extract_base64_images` already avoids this via its
`img_{counter:03d}` scheme.)
Adds regression tests for the same-basename collision and the
identical-source-referenced-twice cases.
CopilotAI review requested due to automatic review settings June 20, 2026 03:16

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a data-loss bug in the Markdown conversion pipeline where copy_relative_images could overwrite one relative image with another when different source paths share the same basename (e.g., a/logo.png and b/logo.png), causing both links to render the same copied file.

Changes:

  • Add per-source destination tracking to dedupe repeated references to the same image and disambiguate basename collisions via {stem}_{n}{suffix}.
  • Add tests covering same-basename/different-source behavior and same-source referenced twice (copy once).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

FileDescription
openkb/images.pyPrevent basename collisions and dedupe repeated references when copying relative images.
tests/test_images.pyAdd regression tests for basename collisions and deduplication behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadopenkb/images.py Outdated
Comment on lines +231 to +232
assigned: dict[Path, str] = {}
taken: set[str] = set()
…g prior files
Review feedback: copy_relative_images relied on the in-call taken set to
disambiguate basenames, but a file already present in images_dir (e.g. from a
prior conversion) was not in taken, so a same-basename source could still
overwrite it. Seed taken from the existing directory contents.

@KylinMountainKylinMountain left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Core fix (first commit) is correct.

The second commit — seeding taken from images_dir, per Copilot's note — is the problem. This dir is per-doc_name, so it only ever holds this doc's own images from a prior run; overwriting them on re-convert is the desired refresh, not data loss. The real bug (two different sources colliding in one pass) is already prevented by taken starting empty. Seeding from disk instead makes re-convert non-idempotent — stale orphans + _1/_2 link churn on every edit.

Suggest dropping the second commit — the first one alone is the right fix.

KylinMountain added a commit that referenced this pull request Jul 2, 2026
Two relative source images with the same basename but different paths
(e.g. a/logo.png and b/logo.png) overwrote each other in
sources/images/<doc>/, collapsing both markdown links onto one file.
Track the destination assigned to each source (so an image referenced
twice is copied once) and suffix genuine basename collisions
(logo.png -> logo_1.png).
Adapted from #122 by @jichaowang02-lang; drops that PR's second commit,
which seeded the taken-name set from the existing images_dir and thereby
broke re-convert idempotency (a changed same-basename image got a fresh
suffix each run, orphaning the old file and churning links).
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
Co-authored-by: jichao wang <jichaowang02@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@jichaowang02-lang@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(images): don't overwrite relative images that share a basename - #122

Closed
jichaowang02-lang wants to merge 2 commits into
VectifyAI:mainfrom
jichaowang02-lang:fix/relative-image-basename-collision
Closed

fix(images): don't overwrite relative images that share a basename#122
jichaowang02-lang wants to merge 2 commits into
VectifyAI:mainfrom
jichaowang02-lang:fix/relative-image-basename-collision

Conversation

@jichaowang02-lang

Copy link
Copy Markdown
Contributor

Summary

copy_relative_images could silently lose an image and render the wrong
one. It named every destination after src.name (the basename only), so two
references to different files that happen to share a basename collide.

Root cause

filename=src.name# basename onlydest=images_dir/filenameshutil.copy2(src, dest) # second same-basename source overwrites the first

For ![a](a/logo.png) + ![b](b/logo.png):

  • both copy to images_dir/logo.png → the second copy2 overwrites the first
    (image A's bytes are gone), and
  • both links are rewritten to sources/images/<doc>/logo.png, so the rendered
    document shows the same image in both places.

extract_base64_images already avoids this by numbering destinations
(img_{counter:03d}); only the relative-image path was affected.

inputbeforeafter
a/logo.png + b/logo.png (distinct)1 file, both links → it, A lost2 files (logo.png, logo_1.png), distinct links
logo.png referenced twice (same file)1 file1 file (deduped, both links agree)

Fix

Track the destination assigned to each source: reuse it when the identical
source is referenced more than once (no duplicate copy), and disambiguate with
a {stem}_{n}{suffix} suffix when a different source would collide on a
name already taken.

Testing

$ pytest tests/test_images.py -q
12 passed
$ ruff check openkb/images.py tests/test_images.py
All checks passed!

Adds test_same_basename_different_dirs_no_overwrite (both images preserved,
links distinct) and test_same_image_referenced_twice_is_copied_once (dedup).

`copy_relative_images` named each destination after `src.name` (the basename
only). Two image references to different files that share a basename — e.g.
`![a](a/logo.png)` and `![b](b/logo.png)` — both copied to
`images_dir/logo.png`, so the second `shutil.copy2` silently overwrote the
first (one image's bytes are lost) and both links were rewritten to the same
path, rendering the same image in both places.
Track an assigned destination per source: reuse it when the identical source
is referenced more than once (no duplicate copy), and disambiguate with a
`{stem}_{n}{suffix}` suffix when a different source would collide on a name
already taken. (`extract_base64_images` already avoids this via its
`img_{counter:03d}` scheme.)
Adds regression tests for the same-basename collision and the
identical-source-referenced-twice cases.
CopilotAI review requested due to automatic review settings June 20, 2026 03:16

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a data-loss bug in the Markdown conversion pipeline where copy_relative_images could overwrite one relative image with another when different source paths share the same basename (e.g., a/logo.png and b/logo.png), causing both links to render the same copied file.

Changes:

  • Add per-source destination tracking to dedupe repeated references to the same image and disambiguate basename collisions via {stem}_{n}{suffix}.
  • Add tests covering same-basename/different-source behavior and same-source referenced twice (copy once).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

FileDescription
openkb/images.pyPrevent basename collisions and dedupe repeated references when copying relative images.
tests/test_images.pyAdd regression tests for basename collisions and deduplication behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadopenkb/images.py Outdated
Comment on lines +231 to +232
assigned: dict[Path, str] = {}
taken: set[str] = set()
…g prior files
Review feedback: copy_relative_images relied on the in-call taken set to
disambiguate basenames, but a file already present in images_dir (e.g. from a
prior conversion) was not in taken, so a same-basename source could still
overwrite it. Seed taken from the existing directory contents.

@KylinMountainKylinMountain left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Core fix (first commit) is correct.

The second commit — seeding taken from images_dir, per Copilot's note — is the problem. This dir is per-doc_name, so it only ever holds this doc's own images from a prior run; overwriting them on re-convert is the desired refresh, not data loss. The real bug (two different sources colliding in one pass) is already prevented by taken starting empty. Seeding from disk instead makes re-convert non-idempotent — stale orphans + _1/_2 link churn on every edit.

Suggest dropping the second commit — the first one alone is the right fix.

KylinMountain added a commit that referenced this pull request Jul 2, 2026
Two relative source images with the same basename but different paths
(e.g. a/logo.png and b/logo.png) overwrote each other in
sources/images/<doc>/, collapsing both markdown links onto one file.
Track the destination assigned to each source (so an image referenced
twice is copied once) and suffix genuine basename collisions
(logo.png -> logo_1.png).
Adapted from #122 by @jichaowang02-lang; drops that PR's second commit,
which seeded the taken-name set from the existing images_dir and thereby
broke re-convert idempotency (a changed same-basename image got a fresh
suffix each run, orphaning the old file and churning links).
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
Co-authored-by: jichao wang <jichaowang02@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@jichaowang02-lang@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(images): don't overwrite relative images that share a basename - #122

Closed
jichaowang02-lang wants to merge 2 commits into
VectifyAI:mainfrom
jichaowang02-lang:fix/relative-image-basename-collision
Closed

fix(images): don't overwrite relative images that share a basename#122
jichaowang02-lang wants to merge 2 commits into
VectifyAI:mainfrom
jichaowang02-lang:fix/relative-image-basename-collision

Conversation

@jichaowang02-lang

Copy link
Copy Markdown
Contributor

Summary

copy_relative_images could silently lose an image and render the wrong
one. It named every destination after src.name (the basename only), so two
references to different files that happen to share a basename collide.

Root cause

filename=src.name# basename onlydest=images_dir/filenameshutil.copy2(src, dest) # second same-basename source overwrites the first

For ![a](a/logo.png) + ![b](b/logo.png):

  • both copy to images_dir/logo.png → the second copy2 overwrites the first
    (image A's bytes are gone), and
  • both links are rewritten to sources/images/<doc>/logo.png, so the rendered
    document shows the same image in both places.

extract_base64_images already avoids this by numbering destinations
(img_{counter:03d}); only the relative-image path was affected.

inputbeforeafter
a/logo.png + b/logo.png (distinct)1 file, both links → it, A lost2 files (logo.png, logo_1.png), distinct links
logo.png referenced twice (same file)1 file1 file (deduped, both links agree)

Fix

Track the destination assigned to each source: reuse it when the identical
source is referenced more than once (no duplicate copy), and disambiguate with
a {stem}_{n}{suffix} suffix when a different source would collide on a
name already taken.

Testing

$ pytest tests/test_images.py -q
12 passed
$ ruff check openkb/images.py tests/test_images.py
All checks passed!

Adds test_same_basename_different_dirs_no_overwrite (both images preserved,
links distinct) and test_same_image_referenced_twice_is_copied_once (dedup).

`copy_relative_images` named each destination after `src.name` (the basename
only). Two image references to different files that share a basename — e.g.
`![a](a/logo.png)` and `![b](b/logo.png)` — both copied to
`images_dir/logo.png`, so the second `shutil.copy2` silently overwrote the
first (one image's bytes are lost) and both links were rewritten to the same
path, rendering the same image in both places.
Track an assigned destination per source: reuse it when the identical source
is referenced more than once (no duplicate copy), and disambiguate with a
`{stem}_{n}{suffix}` suffix when a different source would collide on a name
already taken. (`extract_base64_images` already avoids this via its
`img_{counter:03d}` scheme.)
Adds regression tests for the same-basename collision and the
identical-source-referenced-twice cases.
CopilotAI review requested due to automatic review settings June 20, 2026 03:16

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a data-loss bug in the Markdown conversion pipeline where copy_relative_images could overwrite one relative image with another when different source paths share the same basename (e.g., a/logo.png and b/logo.png), causing both links to render the same copied file.

Changes:

  • Add per-source destination tracking to dedupe repeated references to the same image and disambiguate basename collisions via {stem}_{n}{suffix}.
  • Add tests covering same-basename/different-source behavior and same-source referenced twice (copy once).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

FileDescription
openkb/images.pyPrevent basename collisions and dedupe repeated references when copying relative images.
tests/test_images.pyAdd regression tests for basename collisions and deduplication behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadopenkb/images.py Outdated
Comment on lines +231 to +232
assigned: dict[Path, str] = {}
taken: set[str] = set()
…g prior files
Review feedback: copy_relative_images relied on the in-call taken set to
disambiguate basenames, but a file already present in images_dir (e.g. from a
prior conversion) was not in taken, so a same-basename source could still
overwrite it. Seed taken from the existing directory contents.

@KylinMountainKylinMountain left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Core fix (first commit) is correct.

The second commit — seeding taken from images_dir, per Copilot's note — is the problem. This dir is per-doc_name, so it only ever holds this doc's own images from a prior run; overwriting them on re-convert is the desired refresh, not data loss. The real bug (two different sources colliding in one pass) is already prevented by taken starting empty. Seeding from disk instead makes re-convert non-idempotent — stale orphans + _1/_2 link churn on every edit.

Suggest dropping the second commit — the first one alone is the right fix.

KylinMountain added a commit that referenced this pull request Jul 2, 2026
Two relative source images with the same basename but different paths
(e.g. a/logo.png and b/logo.png) overwrote each other in
sources/images/<doc>/, collapsing both markdown links onto one file.
Track the destination assigned to each source (so an image referenced
twice is copied once) and suffix genuine basename collisions
(logo.png -> logo_1.png).
Adapted from #122 by @jichaowang02-lang; drops that PR's second commit,
which seeded the taken-name set from the existing images_dir and thereby
broke re-convert idempotency (a changed same-basename image got a fresh
suffix each run, orphaning the old file and churning links).
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
Co-authored-by: jichao wang <jichaowang02@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@jichaowang02-lang@KylinMountain