Fossil SCM
Add the /tree URI for showing a hierarchical file listing. The URI works, but there are no hyperlinks to it yet.
Commit
7791b704109adb8951d89ef8a70a21ebcfa30ace
Parent
43a2d0fa70ca4b1…
1 file changed
+286
-3
+286
-3
| --- src/browse.c | ||
| +++ src/browse.c | ||
| @@ -101,11 +101,11 @@ | ||
| 101 | 101 | /* |
| 102 | 102 | ** WEBPAGE: dir |
| 103 | 103 | ** |
| 104 | 104 | ** Query parameters: |
| 105 | 105 | ** |
| 106 | -** name=PATH Directory to display. Required. | |
| 106 | +** name=PATH Directory to display. Optional. Top-level if missing | |
| 107 | 107 | ** ci=LABEL Show only files in this check-in. Optional. |
| 108 | 108 | */ |
| 109 | 109 | void page_dir(void){ |
| 110 | 110 | char *zD = fossil_strdup(P("name")); |
| 111 | 111 | int nD = zD ? strlen(zD)+1 : 0; |
| @@ -156,20 +156,20 @@ | ||
| 156 | 156 | zPrefix = mprintf("%s/", zD); |
| 157 | 157 | if( linkTrunk ){ |
| 158 | 158 | style_submenu_element("Trunk", "Trunk", "%R/dir?name=%t&ci=trunk", |
| 159 | 159 | zD); |
| 160 | 160 | } |
| 161 | - if ( linkTip ){ | |
| 161 | + if( linkTip ){ | |
| 162 | 162 | style_submenu_element("Tip", "Tip", "%R/dir?name=%t&ci=tip", zD); |
| 163 | 163 | } |
| 164 | 164 | }else{ |
| 165 | 165 | blob_append(&dirname, "in the top-level directory", -1); |
| 166 | 166 | zPrefix = ""; |
| 167 | 167 | if( linkTrunk ){ |
| 168 | 168 | style_submenu_element("Trunk", "Trunk", "%R/dir?ci=trunk"); |
| 169 | 169 | } |
| 170 | - if ( linkTip ){ | |
| 170 | + if( linkTip ){ | |
| 171 | 171 | style_submenu_element("Tip", "Tip", "%R/dir?ci=tip"); |
| 172 | 172 | } |
| 173 | 173 | } |
| 174 | 174 | if( zCI ){ |
| 175 | 175 | char zShort[20]; |
| @@ -287,10 +287,293 @@ | ||
| 287 | 287 | db_finalize(&q); |
| 288 | 288 | manifest_destroy(pM); |
| 289 | 289 | @ </ul></td></tr></table> |
| 290 | 290 | style_footer(); |
| 291 | 291 | } |
| 292 | + | |
| 293 | +/* | |
| 294 | +** Objects used by the "tree" webpage. | |
| 295 | +*/ | |
| 296 | +typedef struct FileTreeNode FileTreeNode; | |
| 297 | +typedef struct FileTree FileTree; | |
| 298 | + | |
| 299 | +/* | |
| 300 | +** A single line of the file hierarchy | |
| 301 | +*/ | |
| 302 | +struct FileTreeNode { | |
| 303 | + FileTreeNode *pNext; /* Next line in sequence */ | |
| 304 | + FileTreeNode *pPrev; /* Previous line */ | |
| 305 | + FileTreeNode *pParent; /* Directory containing this line */ | |
| 306 | + char *zName; /* Name of this entry. The "tail" */ | |
| 307 | + char *zFullName; /* Full pathname of this entry */ | |
| 308 | + char *zUuid; /* SHA1 hash of this file. May be NULL. */ | |
| 309 | + unsigned nFullName; /* Length of zFullName */ | |
| 310 | + unsigned iLevel; /* Levels of parent directories */ | |
| 311 | + u8 isDir; /* True if there are children */ | |
| 312 | + u8 isLast; /* True if this is the last child of its parent */ | |
| 313 | +}; | |
| 314 | + | |
| 315 | +/* | |
| 316 | +** A complete file hierarchy | |
| 317 | +*/ | |
| 318 | +struct FileTree { | |
| 319 | + FileTreeNode *pFirst; /* First line of the list */ | |
| 320 | + FileTreeNode *pLast; /* Last line of the list */ | |
| 321 | +}; | |
| 322 | + | |
| 323 | +/* | |
| 324 | +** Add one or more new FileTreeNodes to the FileTree object so that the | |
| 325 | +** leaf object zPathname is at the end of the node list | |
| 326 | +*/ | |
| 327 | +static void tree_add_node( | |
| 328 | + FileTree *pTree, /* Tree into which nodes are added */ | |
| 329 | + const char *zPath, /* The full pathname of file to add */ | |
| 330 | + const char *zUuid /* UUID of the file. Might be NULL. */ | |
| 331 | +){ | |
| 332 | + int i; | |
| 333 | + FileTreeNode *pParent; | |
| 334 | + FileTreeNode *pChild; | |
| 335 | + | |
| 336 | + pChild = pTree->pLast; | |
| 337 | + pParent = pChild ? pChild->pParent : 0; | |
| 338 | + while( pParent!=0 && | |
| 339 | + ( strncmp(pParent->zFullName, zPath, pParent->nFullName)!=0 | |
| 340 | + || zPath[pParent->nFullName]!='/' ) | |
| 341 | + ){ | |
| 342 | + pChild = pParent; | |
| 343 | + pParent = pChild->pParent; | |
| 344 | + } | |
| 345 | + i = pParent ? pParent->nFullName+1 : 0; | |
| 346 | + if( pChild ) pChild->isLast = 0; | |
| 347 | + while( zPath[i] ){ | |
| 348 | + FileTreeNode *pNew; | |
| 349 | + int iStart = i; | |
| 350 | + int nByte; | |
| 351 | + while( zPath[i] && zPath[i]!='/' ){ i++; } | |
| 352 | + nByte = sizeof(*pNew) + i + 1; | |
| 353 | + if( zUuid!=0 && zPath[i]==0 ) nByte += UUID_SIZE+1; | |
| 354 | + pNew = fossil_malloc( nByte ); | |
| 355 | + pNew->zFullName = (char*)&pNew[1]; | |
| 356 | + memcpy(pNew->zFullName, zPath, i); | |
| 357 | + pNew->zFullName[i] = 0; | |
| 358 | + pNew->nFullName = i; | |
| 359 | + if( zUuid!=0 && zPath[i]==0 ){ | |
| 360 | + pNew->zUuid = pNew->zFullName + i + 1; | |
| 361 | + memcpy(pNew->zUuid, zUuid, UUID_SIZE+1); | |
| 362 | + }else{ | |
| 363 | + pNew->zUuid = 0; | |
| 364 | + } | |
| 365 | + pNew->zName = pNew->zFullName + iStart; | |
| 366 | + if( pTree->pLast ){ | |
| 367 | + pTree->pLast->pNext = pNew; | |
| 368 | + }else{ | |
| 369 | + pTree->pFirst = pNew; | |
| 370 | + } | |
| 371 | + pNew->pPrev = pTree->pLast; | |
| 372 | + pNew->pNext = 0; | |
| 373 | + pNew->pParent = pParent; | |
| 374 | + pTree->pLast = pNew; | |
| 375 | + pNew->iLevel = pParent ? pParent->iLevel+1 : 0; | |
| 376 | + pNew->isDir = zPath[i]=='/'; | |
| 377 | + pNew->isLast = 1; | |
| 378 | + while( zPath[i]=='/' ){ i++; } | |
| 379 | + pParent = pNew; | |
| 380 | + } | |
| 381 | +} | |
| 382 | + | |
| 383 | +/* | |
| 384 | +** Render parent lines for pNode | |
| 385 | +*/ | |
| 386 | +static void tree_indentation(FileTreeNode *p){ | |
| 387 | + if( p==0 ) return; | |
| 388 | + tree_indentation(p->pParent); | |
| 389 | + if( p->isLast ){ | |
| 390 | + cgi_append_content(" ", 4); | |
| 391 | + }else{ | |
| 392 | + cgi_append_content("│ ", 11); | |
| 393 | + } | |
| 394 | +} | |
| 395 | + | |
| 396 | + | |
| 397 | +/* | |
| 398 | +** WEBPAGE: tree | |
| 399 | +** | |
| 400 | +** Query parameters: | |
| 401 | +** | |
| 402 | +** name=PATH Directory to display. Optional | |
| 403 | +** ci=LABEL Show only files in this check-in. Optional. | |
| 404 | +** re=REGEXP Show only files matching REGEXP. Optional. | |
| 405 | +*/ | |
| 406 | +void page_tree(void){ | |
| 407 | + char *zD = fossil_strdup(P("name")); | |
| 408 | + int nD = zD ? strlen(zD)+1 : 0; | |
| 409 | + const char *zCI = P("ci"); | |
| 410 | + int rid = 0; | |
| 411 | + char *zUuid = 0; | |
| 412 | + Blob dirname; | |
| 413 | + Manifest *pM = 0; | |
| 414 | + int linkTrunk = 1, linkTip = 1; | |
| 415 | + const char *zRE; | |
| 416 | + ReCompiled *pRE = 0; | |
| 417 | + FileTreeNode *p; | |
| 418 | + FileTree sTree; | |
| 419 | + | |
| 420 | + memset(&sTree, 0, sizeof(sTree)); | |
| 421 | + login_check_credentials(); | |
| 422 | + if( !g.perm.Read ){ login_needed(); return; } | |
| 423 | + while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; } | |
| 424 | + style_header("File List"); | |
| 425 | + sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, | |
| 426 | + pathelementFunc, 0, 0); | |
| 427 | + | |
| 428 | + /* If a regular expression is specified, compile it */ | |
| 429 | + zRE = P("re"); | |
| 430 | + if( zRE ) re_compile(&pRE, zRE, 0); | |
| 431 | + | |
| 432 | + /* If the name= parameter is an empty string, make it a NULL pointer */ | |
| 433 | + if( zD && strlen(zD)==0 ){ zD = 0; } | |
| 434 | + | |
| 435 | + /* If a specific check-in is requested, fetch and parse it. If the | |
| 436 | + ** specific check-in does not exist, clear zCI. zCI==0 will cause all | |
| 437 | + ** files from all check-ins to be displayed. | |
| 438 | + */ | |
| 439 | + if( zCI ){ | |
| 440 | + pM = manifest_get_by_name(zCI, &rid); | |
| 441 | + if( pM ){ | |
| 442 | + int trunkRid = symbolic_name_to_rid("tag:trunk", "ci"); | |
| 443 | + linkTrunk = trunkRid && rid != trunkRid; | |
| 444 | + linkTip = rid != symbolic_name_to_rid("tip", "ci"); | |
| 445 | + zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); | |
| 446 | + }else{ | |
| 447 | + zCI = 0; | |
| 448 | + } | |
| 449 | + } | |
| 450 | + | |
| 451 | + /* Compute the title of the page */ | |
| 452 | + blob_zero(&dirname); | |
| 453 | + if( zD ){ | |
| 454 | + blob_appendf(&dirname, "in directory %h", zD); | |
| 455 | + if( zRE ) blob_appendf(&dirname, " matching \"%s\"", zRE); | |
| 456 | + if( linkTrunk ){ | |
| 457 | + style_submenu_element("Trunk", "Trunk", "%R/tree?name=%t&ci=trunk", | |
| 458 | + zD); | |
| 459 | + } | |
| 460 | + if ( linkTip ){ | |
| 461 | + style_submenu_element("Tip", "Tip", "%R/tree?name=%t&ci=tip", zD); | |
| 462 | + } | |
| 463 | + }else{ | |
| 464 | + if( zRE ){ | |
| 465 | + blob_appendf(&dirname, "matching \"%s\"", zRE); | |
| 466 | + }else{ | |
| 467 | + blob_append(&dirname, "in the top-level directory", -1); | |
| 468 | + } | |
| 469 | + if( linkTrunk ){ | |
| 470 | + style_submenu_element("Trunk", "Trunk", "%R/tree?ci=trunk"); | |
| 471 | + } | |
| 472 | + if ( linkTip ){ | |
| 473 | + style_submenu_element("Tip", "Tip", "%R/tree?ci=tip"); | |
| 474 | + } | |
| 475 | + } | |
| 476 | + if( zCI ){ | |
| 477 | + char zShort[20]; | |
| 478 | + memcpy(zShort, zUuid, 10); | |
| 479 | + zShort[10] = 0; | |
| 480 | + @ <h2>Files of check-in [%z(href("vinfo?name=%T",zUuid))%s(zShort)</a>] | |
| 481 | + @ %s(blob_str(&dirname))</h2> | |
| 482 | + if( zD ){ | |
| 483 | + style_submenu_element("Top", "Top", "%R/tree?ci=%S", zUuid); | |
| 484 | + style_submenu_element("All", "All", "%R/tree?name=%t", zD); | |
| 485 | + }else{ | |
| 486 | + style_submenu_element("All", "All", "%R/tree"); | |
| 487 | + } | |
| 488 | + }else{ | |
| 489 | + @ <h2>The union of all files from all check-ins | |
| 490 | + @ %s(blob_str(&dirname))</h2> | |
| 491 | + } | |
| 492 | + | |
| 493 | + /* Compute the file hierarchy. | |
| 494 | + */ | |
| 495 | + if( zCI ){ | |
| 496 | + Stmt ins, q; | |
| 497 | + ManifestFile *pFile; | |
| 498 | + | |
| 499 | + db_multi_exec( | |
| 500 | + "CREATE TEMP TABLE filelist(" | |
| 501 | + " x TEXT PRIMARY KEY COLLATE nocase," | |
| 502 | + " uuid TEXT" | |
| 503 | + ") WITHOUT ROWID;" | |
| 504 | + ); | |
| 505 | + db_prepare(&ins, "INSERT OR IGNORE INTO filelist VALUES(:f,:u)"); | |
| 506 | + manifest_file_rewind(pM); | |
| 507 | + while( (pFile = manifest_file_next(pM,0))!=0 ){ | |
| 508 | + if( nD>0 | |
| 509 | + && (fossil_strncmp(pFile->zName, zD, nD-1)!=0 | |
| 510 | + || pFile->zName[nD-1]!='/') | |
| 511 | + ){ | |
| 512 | + continue; | |
| 513 | + } | |
| 514 | + if( pRE && re_match(pRE, (const u8*)pFile->zName, -1)==0 ) continue; | |
| 515 | + db_bind_text(&ins, ":f", &pFile->zName[nD]); | |
| 516 | + db_bind_text(&ins, ":u", pFile->zUuid); | |
| 517 | + db_step(&ins); | |
| 518 | + db_reset(&ins); | |
| 519 | + } | |
| 520 | + db_finalize(&ins); | |
| 521 | + db_prepare(&q, "SELECT x, uuid FROM filelist ORDER BY x"); | |
| 522 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 523 | + tree_add_node(&sTree, db_column_text(&q,0), db_column_text(&q,1)); | |
| 524 | + } | |
| 525 | + db_finalize(&q); | |
| 526 | + }else{ | |
| 527 | + Stmt q; | |
| 528 | + db_prepare(&q, "SELECT name FROM filename ORDER BY name COLLATE nocase"); | |
| 529 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 530 | + const char *z = db_column_text(&q, 0); | |
| 531 | + if( nD>0 && (fossil_strncmp(z, zD, nD-1)!=0 || z[nD-1]!='/') ){ | |
| 532 | + continue; | |
| 533 | + } | |
| 534 | + if( pRE && re_match(pRE, (const u8*)z, -1)==0 ) continue; | |
| 535 | + tree_add_node(&sTree, z+nD, 0); | |
| 536 | + } | |
| 537 | + db_finalize(&q); | |
| 538 | + } | |
| 539 | + | |
| 540 | + /* Generate a multi-column table listing the contents of zD[] | |
| 541 | + ** directory. | |
| 542 | + */ | |
| 543 | + @ <pre> | |
| 544 | + if( nD ){ | |
| 545 | + cgi_printf("%.*h\n", nD, zD); | |
| 546 | + }else{ | |
| 547 | + @ . | |
| 548 | + } | |
| 549 | + for(p=sTree.pFirst; p; p=p->pNext){ | |
| 550 | + tree_indentation(p->pParent); | |
| 551 | + if( p->isLast ){ | |
| 552 | + cgi_append_content("└── ", 25); | |
| 553 | + }else{ | |
| 554 | + cgi_append_content("├── ", 25); | |
| 555 | + } | |
| 556 | + if( p->isDir ){ | |
| 557 | + @ %h(p->zName) | |
| 558 | + }else{ | |
| 559 | + char *zLink; | |
| 560 | + if( zCI ){ | |
| 561 | + zLink = href("%R/artifact/%s",p->zUuid); | |
| 562 | + }else{ | |
| 563 | + zLink = href("%R/finfo?name=%T",p->zFullName); | |
| 564 | + } | |
| 565 | + @ %z(zLink)%h(p->zName)</a> | |
| 566 | + } | |
| 567 | + } | |
| 568 | + @ </pre> | |
| 569 | + style_footer(); | |
| 570 | + | |
| 571 | + /* We could free memory used by sTree here if we needed to. But | |
| 572 | + ** the process is about to exit, so doing so would not really accomplish | |
| 573 | + ** anything useful. */ | |
| 574 | +} | |
| 292 | 575 | |
| 293 | 576 | /* |
| 294 | 577 | ** Return a CSS class name based on the given filename's extension. |
| 295 | 578 | ** Result must be freed by the caller. |
| 296 | 579 | **/ |
| 297 | 580 |
| --- src/browse.c | |
| +++ src/browse.c | |
| @@ -101,11 +101,11 @@ | |
| 101 | /* |
| 102 | ** WEBPAGE: dir |
| 103 | ** |
| 104 | ** Query parameters: |
| 105 | ** |
| 106 | ** name=PATH Directory to display. Required. |
| 107 | ** ci=LABEL Show only files in this check-in. Optional. |
| 108 | */ |
| 109 | void page_dir(void){ |
| 110 | char *zD = fossil_strdup(P("name")); |
| 111 | int nD = zD ? strlen(zD)+1 : 0; |
| @@ -156,20 +156,20 @@ | |
| 156 | zPrefix = mprintf("%s/", zD); |
| 157 | if( linkTrunk ){ |
| 158 | style_submenu_element("Trunk", "Trunk", "%R/dir?name=%t&ci=trunk", |
| 159 | zD); |
| 160 | } |
| 161 | if ( linkTip ){ |
| 162 | style_submenu_element("Tip", "Tip", "%R/dir?name=%t&ci=tip", zD); |
| 163 | } |
| 164 | }else{ |
| 165 | blob_append(&dirname, "in the top-level directory", -1); |
| 166 | zPrefix = ""; |
| 167 | if( linkTrunk ){ |
| 168 | style_submenu_element("Trunk", "Trunk", "%R/dir?ci=trunk"); |
| 169 | } |
| 170 | if ( linkTip ){ |
| 171 | style_submenu_element("Tip", "Tip", "%R/dir?ci=tip"); |
| 172 | } |
| 173 | } |
| 174 | if( zCI ){ |
| 175 | char zShort[20]; |
| @@ -287,10 +287,293 @@ | |
| 287 | db_finalize(&q); |
| 288 | manifest_destroy(pM); |
| 289 | @ </ul></td></tr></table> |
| 290 | style_footer(); |
| 291 | } |
| 292 | |
| 293 | /* |
| 294 | ** Return a CSS class name based on the given filename's extension. |
| 295 | ** Result must be freed by the caller. |
| 296 | **/ |
| 297 |
| --- src/browse.c | |
| +++ src/browse.c | |
| @@ -101,11 +101,11 @@ | |
| 101 | /* |
| 102 | ** WEBPAGE: dir |
| 103 | ** |
| 104 | ** Query parameters: |
| 105 | ** |
| 106 | ** name=PATH Directory to display. Optional. Top-level if missing |
| 107 | ** ci=LABEL Show only files in this check-in. Optional. |
| 108 | */ |
| 109 | void page_dir(void){ |
| 110 | char *zD = fossil_strdup(P("name")); |
| 111 | int nD = zD ? strlen(zD)+1 : 0; |
| @@ -156,20 +156,20 @@ | |
| 156 | zPrefix = mprintf("%s/", zD); |
| 157 | if( linkTrunk ){ |
| 158 | style_submenu_element("Trunk", "Trunk", "%R/dir?name=%t&ci=trunk", |
| 159 | zD); |
| 160 | } |
| 161 | if( linkTip ){ |
| 162 | style_submenu_element("Tip", "Tip", "%R/dir?name=%t&ci=tip", zD); |
| 163 | } |
| 164 | }else{ |
| 165 | blob_append(&dirname, "in the top-level directory", -1); |
| 166 | zPrefix = ""; |
| 167 | if( linkTrunk ){ |
| 168 | style_submenu_element("Trunk", "Trunk", "%R/dir?ci=trunk"); |
| 169 | } |
| 170 | if( linkTip ){ |
| 171 | style_submenu_element("Tip", "Tip", "%R/dir?ci=tip"); |
| 172 | } |
| 173 | } |
| 174 | if( zCI ){ |
| 175 | char zShort[20]; |
| @@ -287,10 +287,293 @@ | |
| 287 | db_finalize(&q); |
| 288 | manifest_destroy(pM); |
| 289 | @ </ul></td></tr></table> |
| 290 | style_footer(); |
| 291 | } |
| 292 | |
| 293 | /* |
| 294 | ** Objects used by the "tree" webpage. |
| 295 | */ |
| 296 | typedef struct FileTreeNode FileTreeNode; |
| 297 | typedef struct FileTree FileTree; |
| 298 | |
| 299 | /* |
| 300 | ** A single line of the file hierarchy |
| 301 | */ |
| 302 | struct FileTreeNode { |
| 303 | FileTreeNode *pNext; /* Next line in sequence */ |
| 304 | FileTreeNode *pPrev; /* Previous line */ |
| 305 | FileTreeNode *pParent; /* Directory containing this line */ |
| 306 | char *zName; /* Name of this entry. The "tail" */ |
| 307 | char *zFullName; /* Full pathname of this entry */ |
| 308 | char *zUuid; /* SHA1 hash of this file. May be NULL. */ |
| 309 | unsigned nFullName; /* Length of zFullName */ |
| 310 | unsigned iLevel; /* Levels of parent directories */ |
| 311 | u8 isDir; /* True if there are children */ |
| 312 | u8 isLast; /* True if this is the last child of its parent */ |
| 313 | }; |
| 314 | |
| 315 | /* |
| 316 | ** A complete file hierarchy |
| 317 | */ |
| 318 | struct FileTree { |
| 319 | FileTreeNode *pFirst; /* First line of the list */ |
| 320 | FileTreeNode *pLast; /* Last line of the list */ |
| 321 | }; |
| 322 | |
| 323 | /* |
| 324 | ** Add one or more new FileTreeNodes to the FileTree object so that the |
| 325 | ** leaf object zPathname is at the end of the node list |
| 326 | */ |
| 327 | static void tree_add_node( |
| 328 | FileTree *pTree, /* Tree into which nodes are added */ |
| 329 | const char *zPath, /* The full pathname of file to add */ |
| 330 | const char *zUuid /* UUID of the file. Might be NULL. */ |
| 331 | ){ |
| 332 | int i; |
| 333 | FileTreeNode *pParent; |
| 334 | FileTreeNode *pChild; |
| 335 | |
| 336 | pChild = pTree->pLast; |
| 337 | pParent = pChild ? pChild->pParent : 0; |
| 338 | while( pParent!=0 && |
| 339 | ( strncmp(pParent->zFullName, zPath, pParent->nFullName)!=0 |
| 340 | || zPath[pParent->nFullName]!='/' ) |
| 341 | ){ |
| 342 | pChild = pParent; |
| 343 | pParent = pChild->pParent; |
| 344 | } |
| 345 | i = pParent ? pParent->nFullName+1 : 0; |
| 346 | if( pChild ) pChild->isLast = 0; |
| 347 | while( zPath[i] ){ |
| 348 | FileTreeNode *pNew; |
| 349 | int iStart = i; |
| 350 | int nByte; |
| 351 | while( zPath[i] && zPath[i]!='/' ){ i++; } |
| 352 | nByte = sizeof(*pNew) + i + 1; |
| 353 | if( zUuid!=0 && zPath[i]==0 ) nByte += UUID_SIZE+1; |
| 354 | pNew = fossil_malloc( nByte ); |
| 355 | pNew->zFullName = (char*)&pNew[1]; |
| 356 | memcpy(pNew->zFullName, zPath, i); |
| 357 | pNew->zFullName[i] = 0; |
| 358 | pNew->nFullName = i; |
| 359 | if( zUuid!=0 && zPath[i]==0 ){ |
| 360 | pNew->zUuid = pNew->zFullName + i + 1; |
| 361 | memcpy(pNew->zUuid, zUuid, UUID_SIZE+1); |
| 362 | }else{ |
| 363 | pNew->zUuid = 0; |
| 364 | } |
| 365 | pNew->zName = pNew->zFullName + iStart; |
| 366 | if( pTree->pLast ){ |
| 367 | pTree->pLast->pNext = pNew; |
| 368 | }else{ |
| 369 | pTree->pFirst = pNew; |
| 370 | } |
| 371 | pNew->pPrev = pTree->pLast; |
| 372 | pNew->pNext = 0; |
| 373 | pNew->pParent = pParent; |
| 374 | pTree->pLast = pNew; |
| 375 | pNew->iLevel = pParent ? pParent->iLevel+1 : 0; |
| 376 | pNew->isDir = zPath[i]=='/'; |
| 377 | pNew->isLast = 1; |
| 378 | while( zPath[i]=='/' ){ i++; } |
| 379 | pParent = pNew; |
| 380 | } |
| 381 | } |
| 382 | |
| 383 | /* |
| 384 | ** Render parent lines for pNode |
| 385 | */ |
| 386 | static void tree_indentation(FileTreeNode *p){ |
| 387 | if( p==0 ) return; |
| 388 | tree_indentation(p->pParent); |
| 389 | if( p->isLast ){ |
| 390 | cgi_append_content(" ", 4); |
| 391 | }else{ |
| 392 | cgi_append_content("│ ", 11); |
| 393 | } |
| 394 | } |
| 395 | |
| 396 | |
| 397 | /* |
| 398 | ** WEBPAGE: tree |
| 399 | ** |
| 400 | ** Query parameters: |
| 401 | ** |
| 402 | ** name=PATH Directory to display. Optional |
| 403 | ** ci=LABEL Show only files in this check-in. Optional. |
| 404 | ** re=REGEXP Show only files matching REGEXP. Optional. |
| 405 | */ |
| 406 | void page_tree(void){ |
| 407 | char *zD = fossil_strdup(P("name")); |
| 408 | int nD = zD ? strlen(zD)+1 : 0; |
| 409 | const char *zCI = P("ci"); |
| 410 | int rid = 0; |
| 411 | char *zUuid = 0; |
| 412 | Blob dirname; |
| 413 | Manifest *pM = 0; |
| 414 | int linkTrunk = 1, linkTip = 1; |
| 415 | const char *zRE; |
| 416 | ReCompiled *pRE = 0; |
| 417 | FileTreeNode *p; |
| 418 | FileTree sTree; |
| 419 | |
| 420 | memset(&sTree, 0, sizeof(sTree)); |
| 421 | login_check_credentials(); |
| 422 | if( !g.perm.Read ){ login_needed(); return; } |
| 423 | while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; } |
| 424 | style_header("File List"); |
| 425 | sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, |
| 426 | pathelementFunc, 0, 0); |
| 427 | |
| 428 | /* If a regular expression is specified, compile it */ |
| 429 | zRE = P("re"); |
| 430 | if( zRE ) re_compile(&pRE, zRE, 0); |
| 431 | |
| 432 | /* If the name= parameter is an empty string, make it a NULL pointer */ |
| 433 | if( zD && strlen(zD)==0 ){ zD = 0; } |
| 434 | |
| 435 | /* If a specific check-in is requested, fetch and parse it. If the |
| 436 | ** specific check-in does not exist, clear zCI. zCI==0 will cause all |
| 437 | ** files from all check-ins to be displayed. |
| 438 | */ |
| 439 | if( zCI ){ |
| 440 | pM = manifest_get_by_name(zCI, &rid); |
| 441 | if( pM ){ |
| 442 | int trunkRid = symbolic_name_to_rid("tag:trunk", "ci"); |
| 443 | linkTrunk = trunkRid && rid != trunkRid; |
| 444 | linkTip = rid != symbolic_name_to_rid("tip", "ci"); |
| 445 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 446 | }else{ |
| 447 | zCI = 0; |
| 448 | } |
| 449 | } |
| 450 | |
| 451 | /* Compute the title of the page */ |
| 452 | blob_zero(&dirname); |
| 453 | if( zD ){ |
| 454 | blob_appendf(&dirname, "in directory %h", zD); |
| 455 | if( zRE ) blob_appendf(&dirname, " matching \"%s\"", zRE); |
| 456 | if( linkTrunk ){ |
| 457 | style_submenu_element("Trunk", "Trunk", "%R/tree?name=%t&ci=trunk", |
| 458 | zD); |
| 459 | } |
| 460 | if ( linkTip ){ |
| 461 | style_submenu_element("Tip", "Tip", "%R/tree?name=%t&ci=tip", zD); |
| 462 | } |
| 463 | }else{ |
| 464 | if( zRE ){ |
| 465 | blob_appendf(&dirname, "matching \"%s\"", zRE); |
| 466 | }else{ |
| 467 | blob_append(&dirname, "in the top-level directory", -1); |
| 468 | } |
| 469 | if( linkTrunk ){ |
| 470 | style_submenu_element("Trunk", "Trunk", "%R/tree?ci=trunk"); |
| 471 | } |
| 472 | if ( linkTip ){ |
| 473 | style_submenu_element("Tip", "Tip", "%R/tree?ci=tip"); |
| 474 | } |
| 475 | } |
| 476 | if( zCI ){ |
| 477 | char zShort[20]; |
| 478 | memcpy(zShort, zUuid, 10); |
| 479 | zShort[10] = 0; |
| 480 | @ <h2>Files of check-in [%z(href("vinfo?name=%T",zUuid))%s(zShort)</a>] |
| 481 | @ %s(blob_str(&dirname))</h2> |
| 482 | if( zD ){ |
| 483 | style_submenu_element("Top", "Top", "%R/tree?ci=%S", zUuid); |
| 484 | style_submenu_element("All", "All", "%R/tree?name=%t", zD); |
| 485 | }else{ |
| 486 | style_submenu_element("All", "All", "%R/tree"); |
| 487 | } |
| 488 | }else{ |
| 489 | @ <h2>The union of all files from all check-ins |
| 490 | @ %s(blob_str(&dirname))</h2> |
| 491 | } |
| 492 | |
| 493 | /* Compute the file hierarchy. |
| 494 | */ |
| 495 | if( zCI ){ |
| 496 | Stmt ins, q; |
| 497 | ManifestFile *pFile; |
| 498 | |
| 499 | db_multi_exec( |
| 500 | "CREATE TEMP TABLE filelist(" |
| 501 | " x TEXT PRIMARY KEY COLLATE nocase," |
| 502 | " uuid TEXT" |
| 503 | ") WITHOUT ROWID;" |
| 504 | ); |
| 505 | db_prepare(&ins, "INSERT OR IGNORE INTO filelist VALUES(:f,:u)"); |
| 506 | manifest_file_rewind(pM); |
| 507 | while( (pFile = manifest_file_next(pM,0))!=0 ){ |
| 508 | if( nD>0 |
| 509 | && (fossil_strncmp(pFile->zName, zD, nD-1)!=0 |
| 510 | || pFile->zName[nD-1]!='/') |
| 511 | ){ |
| 512 | continue; |
| 513 | } |
| 514 | if( pRE && re_match(pRE, (const u8*)pFile->zName, -1)==0 ) continue; |
| 515 | db_bind_text(&ins, ":f", &pFile->zName[nD]); |
| 516 | db_bind_text(&ins, ":u", pFile->zUuid); |
| 517 | db_step(&ins); |
| 518 | db_reset(&ins); |
| 519 | } |
| 520 | db_finalize(&ins); |
| 521 | db_prepare(&q, "SELECT x, uuid FROM filelist ORDER BY x"); |
| 522 | while( db_step(&q)==SQLITE_ROW ){ |
| 523 | tree_add_node(&sTree, db_column_text(&q,0), db_column_text(&q,1)); |
| 524 | } |
| 525 | db_finalize(&q); |
| 526 | }else{ |
| 527 | Stmt q; |
| 528 | db_prepare(&q, "SELECT name FROM filename ORDER BY name COLLATE nocase"); |
| 529 | while( db_step(&q)==SQLITE_ROW ){ |
| 530 | const char *z = db_column_text(&q, 0); |
| 531 | if( nD>0 && (fossil_strncmp(z, zD, nD-1)!=0 || z[nD-1]!='/') ){ |
| 532 | continue; |
| 533 | } |
| 534 | if( pRE && re_match(pRE, (const u8*)z, -1)==0 ) continue; |
| 535 | tree_add_node(&sTree, z+nD, 0); |
| 536 | } |
| 537 | db_finalize(&q); |
| 538 | } |
| 539 | |
| 540 | /* Generate a multi-column table listing the contents of zD[] |
| 541 | ** directory. |
| 542 | */ |
| 543 | @ <pre> |
| 544 | if( nD ){ |
| 545 | cgi_printf("%.*h\n", nD, zD); |
| 546 | }else{ |
| 547 | @ . |
| 548 | } |
| 549 | for(p=sTree.pFirst; p; p=p->pNext){ |
| 550 | tree_indentation(p->pParent); |
| 551 | if( p->isLast ){ |
| 552 | cgi_append_content("└── ", 25); |
| 553 | }else{ |
| 554 | cgi_append_content("├── ", 25); |
| 555 | } |
| 556 | if( p->isDir ){ |
| 557 | @ %h(p->zName) |
| 558 | }else{ |
| 559 | char *zLink; |
| 560 | if( zCI ){ |
| 561 | zLink = href("%R/artifact/%s",p->zUuid); |
| 562 | }else{ |
| 563 | zLink = href("%R/finfo?name=%T",p->zFullName); |
| 564 | } |
| 565 | @ %z(zLink)%h(p->zName)</a> |
| 566 | } |
| 567 | } |
| 568 | @ </pre> |
| 569 | style_footer(); |
| 570 | |
| 571 | /* We could free memory used by sTree here if we needed to. But |
| 572 | ** the process is about to exit, so doing so would not really accomplish |
| 573 | ** anything useful. */ |
| 574 | } |
| 575 | |
| 576 | /* |
| 577 | ** Return a CSS class name based on the given filename's extension. |
| 578 | ** Result must be freed by the caller. |
| 579 | **/ |
| 580 |