mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
Merge pull request #2602 from bline/fix/doc-link-moved-directory
fix(doc-links): report references into a moved directory as missing (#2588)
This commit is contained in:
2 files changed
+94
-7
No files matched your search
@@ -16,16 +16,18 @@
|
||||
* document wrote the path.
|
||||
*
|
||||
* Unresolved:
|
||||
* missing the path is not in the graph, but its directory is: the
|
||||
* document names something that is not there (doc rot)
|
||||
* missing the path is not in the graph, but its directory is, or a
|
||||
* directory above it that the reference wrote is: the document
|
||||
* names something that is not there (doc rot; a moved or
|
||||
* renamed directory)
|
||||
* ambiguous a file name alone that is not next to the document but exists
|
||||
* elsewhere in the repository: the document does not say which
|
||||
* Not a reference to this repository at all (LOCAL: no edge, no row): a link
|
||||
* back into the same document; a path that is neither indexed nor of a type
|
||||
* the indexer reads (an image, an archive, an extensionless page); a path
|
||||
* whose directory is not in the repository either (a file a tutorial asks
|
||||
* the reader to create, a system path, a path above the root); and, in a
|
||||
* code span, a rooted single segment without a file extension, which is a
|
||||
* none of whose directories is in the repository either (a file a tutorial
|
||||
* asks the reader to create, a system path, a path above the root); and, in
|
||||
* a code span, a rooted single segment without a file extension, which is a
|
||||
* URL route (`/docs`, `/health`), not the directory of that name.
|
||||
*/
|
||||
#include "pipeline/doc_links.h"
|
||||
@@ -408,6 +410,25 @@ static const cbm_gbuf_node_t *mdr_folder(const mdr_index_t *x, const cbm_gbuf_t
|
||||
return path[0] ? (const cbm_gbuf_node_t *)cbm_ht_get(x->folders, path) : NULL;
|
||||
}
|
||||
|
||||
/* Whether a directory above `path` that the reference itself wrote, one
|
||||
* longer than the `base_len` bytes it was read from, is a Folder. */
|
||||
static bool mdr_written_ancestor(const mdr_index_t *x, const cbm_gbuf_t *graph, const char *path,
|
||||
size_t base_len) {
|
||||
char dir[MDR_PATH_CAP];
|
||||
size_t n = strlen(path);
|
||||
if (n >= sizeof(dir)) {
|
||||
return false;
|
||||
}
|
||||
memcpy(dir, path, n + SKIP_ONE);
|
||||
for (char *s = strrchr(dir, '/'); s && (size_t)(s - dir) > base_len; s = strrchr(dir, '/')) {
|
||||
*s = '\0';
|
||||
if (mdr_folder(x, graph, dir)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool mdr_code_label(const char *label) {
|
||||
for (size_t i = 0; label && MDR_CODE_LABELS[i]; i++) {
|
||||
if (strcmp(label, MDR_CODE_LABELS[i]) == 0) {
|
||||
@@ -1126,8 +1147,19 @@ static void mdr_resolve_path(const mdr_index_t *x, const cbm_gbuf_t *graph, cons
|
||||
char tries[MDR_TRIES][MDR_PATH_CAP];
|
||||
int nt = 0;
|
||||
int attempted = 0;
|
||||
size_t base_len[MDR_TRIES]; /* the directory each try reads from, before what p wrote */
|
||||
if (!rooted) {
|
||||
attempted++;
|
||||
char lead[MDR_PATH_CAP]; /* p's leading `./` and `../` segments */
|
||||
char base[MDR_PATH_CAP];
|
||||
size_t ll = 0;
|
||||
while (ll < sizeof(lead) - PAIR_LEN - 1 && (strncmp(p + ll, "./", PAIR_LEN) == 0 ||
|
||||
strncmp(p + ll, "../", PAIR_LEN + 1) == 0)) {
|
||||
ll += p[ll + 1] == '/' ? PAIR_LEN : PAIR_LEN + 1;
|
||||
}
|
||||
memcpy(lead, p, ll);
|
||||
lead[ll] = '\0';
|
||||
base_len[nt] = mdr_join(doc_dir, lead, base, sizeof(base)) ? strlen(base) : 0;
|
||||
nt += mdr_join(doc_dir, p, tries[nt], MDR_PATH_CAP);
|
||||
}
|
||||
if (!relative && !r->name_only) {
|
||||
@@ -1137,7 +1169,7 @@ static void mdr_resolve_path(const mdr_index_t *x, const cbm_gbuf_t *graph, cons
|
||||
q++;
|
||||
}
|
||||
if (mdr_join("", q, tries[nt], MDR_PATH_CAP) && (nt == 0 || strcmp(tries[0], tries[nt]))) {
|
||||
nt++;
|
||||
base_len[nt++] = 0;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < nt; i++) {
|
||||
@@ -1174,7 +1206,8 @@ static void mdr_resolve_path(const mdr_index_t *x, const cbm_gbuf_t *graph, cons
|
||||
return;
|
||||
}
|
||||
/* doc rot when the reference points into the repository: its directory is
|
||||
* there (a bare name in a link: the document's own directory) */
|
||||
* there (a bare name in a link: the document's own directory), or a
|
||||
* directory above it that the reference wrote is (one moved or renamed) */
|
||||
for (int i = 0; i < nt; i++) {
|
||||
char parent[MDR_PATH_CAP];
|
||||
const char *ps = strrchr(tries[i], '/');
|
||||
@@ -1185,6 +1218,10 @@ static void mdr_resolve_path(const mdr_index_t *x, const cbm_gbuf_t *graph, cons
|
||||
out->reason = CBM_DOCLINK_REASON_MISSING;
|
||||
return;
|
||||
}
|
||||
if (pl > 0 && strchr(p, '/') && mdr_written_ancestor(x, graph, tries[i], base_len[i])) {
|
||||
out->reason = CBM_DOCLINK_REASON_MISSING;
|
||||
return;
|
||||
}
|
||||
if (pl == 0 && r->link && !rooted) {
|
||||
out->reason = CBM_DOCLINK_REASON_MISSING;
|
||||
return;
|
||||
|
||||
@@ -444,6 +444,55 @@ TEST(doc_links_md_incremental) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* #2588: a reference into a directory that was moved or renamed named a
|
||||
* path whose directory is gone, so it counted as no reference to this
|
||||
* repository: no edge and no row. A directory above it that the reference
|
||||
* wrote is still there, so it is doc rot. A path none of whose directories
|
||||
* the repository has stays no reference. */
|
||||
TEST(doc_links_md_moved_directory_issue2588) {
|
||||
char tmp[256];
|
||||
snprintf(tmp, sizeof(tmp), "/tmp/cbm_dlmd_mv_XXXXXX");
|
||||
ASSERT_NOT_NULL(cbm_mkdtemp(tmp));
|
||||
char repo[512];
|
||||
snprintf(repo, sizeof(repo), "%s/repo", tmp);
|
||||
th_write_file(TH_PATH(repo, "docs/notes.md"), "# Notes\n"
|
||||
"\n"
|
||||
"See `src/util/rounding.py::clamp` and "
|
||||
"[rounding](../src/util/rounding.py).\n"
|
||||
"Create `scratch/new/thing.py` first.\n");
|
||||
th_write_file(TH_PATH(repo, "src/ledger.py"), "def balance(ledger):\n"
|
||||
" return sum(ledger)\n");
|
||||
th_write_file(TH_PATH(repo, "src/util/rounding.py"), "def clamp(x, lo, hi):\n"
|
||||
" return max(lo, min(hi, x))\n");
|
||||
char db[512];
|
||||
char full_db[512];
|
||||
snprintf(db, sizeof(db), "%s/inc.db", tmp);
|
||||
snprintf(full_db, sizeof(full_db), "%s/full.db", tmp);
|
||||
ASSERT_EQ(dm_index(repo, db, NULL), 0);
|
||||
char props[512];
|
||||
char reason[64];
|
||||
ASSERT_EQ(md_edge(db, "Notes", "rounding.clamp", props, sizeof(props)), 1);
|
||||
|
||||
/* src/util moves to src/helpers: both references are missing, on the
|
||||
* incremental route as in a full index */
|
||||
th_write_file(TH_PATH(repo, "src/helpers/rounding.py"), "def clamp(x, lo, hi):\n"
|
||||
" return max(lo, min(hi, x))\n");
|
||||
ASSERT_EQ(th_rmtree(TH_PATH(repo, "src/util")), 0);
|
||||
ASSERT_EQ(dm_step(repo, db, full_db, "directory moved", CBM_INCREMENTAL_ROUTE_FORCED_FULL), 0);
|
||||
ASSERT_EQ(md_edge(db, "Notes", "rounding.clamp", props, sizeof(props)), 0);
|
||||
dm_row(db, "docs/notes.md", "src/util/rounding.py::clamp", reason, sizeof(reason), NULL, 0);
|
||||
ASSERT_STR_EQ(reason, "missing");
|
||||
dm_row(db, "docs/notes.md", "../src/util/rounding.py", reason, sizeof(reason), NULL, 0);
|
||||
ASSERT_STR_EQ(reason, "missing");
|
||||
|
||||
/* no directory of it in the repository: still no reference */
|
||||
dm_row(db, "docs/notes.md", "scratch/new/thing.py", reason, sizeof(reason), NULL, 0);
|
||||
ASSERT_STR_EQ(reason, "");
|
||||
|
||||
th_cleanup(tmp);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ── ADRs ────────────────────────────────────────────────────────── */
|
||||
|
||||
static void md_write_adrs(const char *tmp, const char *first_status) {
|
||||
@@ -866,6 +915,7 @@ SUITE(doc_links_md) {
|
||||
RUN_TEST(doc_links_md_classify_span);
|
||||
RUN_TEST(doc_links_md_resolve);
|
||||
RUN_TEST(doc_links_md_incremental);
|
||||
RUN_TEST(doc_links_md_moved_directory_issue2588);
|
||||
RUN_TEST(doc_links_md_adr);
|
||||
RUN_TEST(doc_links_md_adr_label_choice);
|
||||
RUN_TEST(doc_links_md_adr_reported);
|
||||
|
||||
Reference in new issue
Block a user