Artifact [7a0da2e572]
Not logged in

Artifact 7a0da2e572eea318ea25e4fbef3e4f071f345150366afc3d42d64f2f43d9d9a3:


/* -*- 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;
}