/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* Copyright 2013-2021 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt SPDX-License-Identifier: BSD-2-Clause-FreeBSD SPDX-FileCopyrightText: 2021 The Libfossil Authors SPDX-ArtifactOfProjectName: Libfossil SPDX-FileType: Code Heavily indebted to the Fossil SCM project (https://fossil-scm.org). ***************************************************************************** This file implements a basic 'ls' for in-repo content. */ #include "libfossil.h" static struct LsApp { const char * glob; fsl_list globs; bool invertGlob; } LsApp = { NULL, fsl_list_empty_m, false/*invertGlob*/ }; static char ls_matches(char const * name){ if(!LsApp.globs.used) return 1; else{ char const rc = fsl_glob_list_matches(&LsApp.globs, name) ? 1 : 0; return LsApp.invertGlob ? !rc : rc; } } /** A fsl_card_F_visitor_f() implementation which outputs state from fc to the fossil output channel. */ static int ls_F_card_v(fsl_card_F const * fc, void * state){ char show; if(!fc->uuid) return 0 /* was removed in this manifest */; show = ls_matches(fc->name); if(show){ char perm; if(FSL_FILE_PERM_EXE & fc->perm) perm = 'x'; else if(FSL_FILE_PERM_LINK & fc->perm) perm = 'L'; else perm = '-'; if(fcli_is_verbose()){ f_out("%-8"FSL_ID_T_PFMT, fsl_uuid_to_rid(fcli_cx(), fc->uuid)); } f_out("%.*s %c %s\n", 12, fc->uuid, perm, fc->name); } return 0; } int main(int argc, char const * const * argv ){ int rc = 0; const char * lsVersion = NULL; bool showCheckouts = false; fsl_buffer buf = fsl_buffer_empty; fsl_cx * f; fsl_deck deck = fsl_deck_empty; fsl_deck * d = &deck; fcli_cliflag FCliFlags[] = { FCLI_FLAG("v","version","version",&lsVersion, "The version to list. Default is 'current' if there is a checkout, " "else 'trunk'."), FCLI_FLAG("g","glob","glob-list",&LsApp.glob, "List only filenames matching the given " "list of space-or-comma-separated glob pattern All patterns must " "be provided as a single string argument, so be sure to quote them " "if your shell might resolve them as wildcards."), FCLI_FLAG_BOOL(0,"invert",&LsApp.invertGlob, "Inverts the matching, such that only files not matching one of " "the globs are listed."), FCLI_FLAG_BOOL(0,"checkouts", &showCheckouts, "List all open checkouts to this repo instead of its files. " "Note that checkouts opened via different paths (via symlinks) " "are not detected here."), fcli_cliflag_empty_m }; fcli_help_info FCliHelp = { "Lists files in a fossil repository.", NULL, NULL }; rc = fcli_setup_v2(argc, argv, FCliFlags, &FCliHelp); if(rc) goto end; /* Set up/validate args... */ f = fcli_cx(); if(!fsl_cx_db_repo(f)){ rc = fcli_err_set(FSL_RC_NOT_A_REPO, "Requires a repository db. See --help."); goto end; } if(LsApp.glob){ fsl_glob_list_parse(&LsApp.globs, LsApp.glob); } LsApp.invertGlob = fcli_flag2("v","invert", NULL); if(fcli_has_unused_flags(0)) goto end; if( showCheckouts ){ int counter = 0; const char * zRepo = fsl_cx_db_file_repo(f, NULL); rc = fsl_config_open( f, NULL ); if( rc ) goto end; rc = fsl_file_canonical_name( zRepo, &buf, false ); if( rc ) goto end; FCLI_VN(1,("Repo filename: %s\n", zRepo)); FCLI_VN(1,("Absolute repo filename: %b\n", &buf)); fsl_db * const dbCfg = fsl_cx_db_config(f); assert( dbCfg ); FCLI_VN(1,("Config db: %s\n", dbCfg->filename)); fsl_stmt q = fsl_stmt_empty; rc = fsl_db_prepare(dbCfg, &q, "SELECT substr(name,7) FROM global_config " "WHERE value=%B ORDER BY value", &buf); if(rc) goto end_show; /* Potential TODO: iterate over all global_config.value entries matching 'ckout:%', canonicalize their names to resolve symlinks (do we have code for that? i don't think so.), and also show matches which open the repo via another (symlinked) path. */ while( FSL_RC_STEP_ROW==(rc = fsl_stmt_step(&q)) ){ f_out("%s\n", fsl_stmt_g_text(&q, 0, NULL)); ++counter; } if( FSL_RC_STEP_DONE==rc ){ rc = 0; if( 0==counter ){ f_out("No checkouts found.\n"); } } end_show: if( rc ){ assert( dbCfg ); fsl_cx_uplift_db_error(f, dbCfg); } fsl_stmt_finalize(&q); }else{ /* List files... */ if(!lsVersion){ lsVersion = fsl_cx_db_ckout(f) ? "current" : "trunk"; } rc = fsl_deck_load_sym(f, d, lsVersion, FSL_SATYPE_CHECKIN); if(rc) goto end; f_out("File list from manifest version '%s' [%.*z] " "(RID %"FSL_ID_T_PFMT")...\n", lsVersion, 12, fsl_rid_to_uuid(f, d->rid), d->rid); if(d->B.uuid){ f_out("This is a delta manifest from baseline [%.*s].\n", 12, d->B.uuid); } if(fcli_is_verbose()) f_out("RID "); f_out("%-12s P Name\n", "UUID"); rc = fsl_deck_F_foreach(d, ls_F_card_v, NULL); } end: fsl_buffer_clear(&buf); fsl_glob_list_clear(&LsApp.globs); fsl_deck_finalize(d); rc = fcli_end_of_main(rc); return rc; }