Login
Documentation
Login
/* -*- 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 checkin [test] app using the libfossil API.
*/

#include "fossil-scm/fossil-cli.h" /* Fossil App mini-framework */
#include "fossil-scm/fossil-internal.h" /* only for testing/debugging */

static void fapp_local_help(){
#if 0
  puts("KNOWN BUGS:\n");
  puts("Currently none known.\n");
#endif
}

/**
   A fsl_stmt_each_f() impl which simply outputs row data in tabular
   form via f_out(). If state is not NULL then it must be
   a (fsl_size_t*), to which the row count is written.
 */
static int stmt_each_dump( fsl_stmt * stmt, void * state ){
  int i;
  char const * sep = "\t";
  fsl_size_t * counter = (fsl_size_t*)state;
  if(1==stmt->rowCount){
    for( i = 0; i < stmt->colCount; ++i ){
      f_out("%s%s", fsl_stmt_col_name(stmt, i),
            (i==stmt->colCount-1) ? "" : sep);
    }
    f_out("\n");
  }
  for( i = 0; i < stmt->colCount; ++i ){
    char const * val = fsl_stmt_g_text(stmt, i, NULL);
    f_out("%s%s", val ? val : "NULL",
          (i==stmt->colCount-1) ? "" : sep);
  }
  f_out("\n");
  if(counter) *counter = stmt->rowCount;
  return 0;
}


/**
    Just experimenting with fsl_xlink_listener() and friends.
*/
static int my_xlink_f(fsl_deck * d, void * state){
  FCLI_V(("Crosslink callback for %s artifact [%.12s] (RID %"FSL_ID_T_PFMT")\n",
           fsl_satype_cstr(d->type), d->uuid, d->rid));
  return 0;
}


/*
Reminder: name of a file by its content RID:

SELECT fn.name
FROM filename fn, mlink ml
WHERE fn.fnid=ml.fnid
AND ml.fid=$contentRid
*/
int main(int argc, char const * const * argv ){
  int rc = 0;
  fsl_cx * f;
  char inTrans = 0;
  fsl_db * db;
  fsl_id_t ckoutId = 0;
  const char * cMsg = NULL;
  const char * cBranch = NULL;
  const char * fname = NULL;
  const char * cMimeType = NULL;
  const char * cDumpMf = NULL;
  const char * cColor = NULL;
  fsl_checkin_opt opt = fsl_checkin_opt_empty;
  fsl_id_t newRid = 0;
  fsl_uuid_str newUuid = NULL;
  fsl_size_t fileArgCount = 0;
  fsl_size_t enqueuedArgCount = 0;
  bool fNoRCard = false;
  bool fBaseline = false;
  bool dryRun = true;
  bool fDoIt = false;

  fcli_cliflag FCliFlags[] = {
    FCLI_FLAG_BOOL("y","do-it", &fDoIt,
                   "Disable dry-run mode (which is the default)."),
    FCLI_FLAG("m","message","text",&cMsg, "Commit message."),
    FCLI_FLAG("b","branch","branch-name",&cBranch, "New branch name."),
    FCLI_FLAG("bg","bg-color","color", &cBranch, "Timeline entry background color."),
    FCLI_FLAG("d","dump","filename",&cDumpMf, "Dump generated artifact to this file."),
    FCLI_FLAG_BOOL("i","integrate",&opt.integrate,
                   "Close all merge parents, not just integrate-merges."),
    FCLI_FLAG_BOOL(0,"baseline",&fBaseline,
                   "Force creationg of a baseline checkin artifact, not a delta. "
                   "Default is automatic determination."),
    FCLI_FLAG_BOOL("r","no-r-card",&fNoRCard,
                   "Disable calculation of the R-card."),
    FCLI_FLAG("mt","mime-type","mimetype",&cMimeType,
              "Mime type of the checkin message. ONLY FOR TESTING: "
              "Fossil currently only supports fossil-wiki-format messages."),
    fcli_cliflag_empty_m
  };
  fcli_help_info FCliHelp = {
  "Performs a checkin of local changes.",
  "[file1...fileN]",
  fapp_local_help
  };
  fcli.cliFlags = FCliFlags;
  fcli.appHelp = &FCliHelp;

  rc = fcli_setup(argc, argv);
  if(FCLI_RC_HELP==rc) /* --help */ return 0;
  else if(rc) goto end;
  opt.calcRCard = !fNoRCard;
  f = fcli.f;
  db = fsl_cx_db_checkout(f);
  if(!db){
    rc = fsl_cx_err_set(f, FSL_RC_NOT_A_CHECKOUT,
                        "This app requires a checkout db.");
    goto end;
  }

  dryRun = !fDoIt;

  if(dryRun){
    f_out("For safety reasons (until this code is stable), "
          "checking in runs in dry-run mode unless the "
          "-y|--do-it flag is provided.\n");
  }

  if(fBaseline) opt.deltaPolicy = 0;
  if(fcli_has_unused_flags(0)) goto end;

  if(!cMsg){
    rc = fcli_err_set(FSL_RC_MISUSE,
                      "Commit message (-m|--message MSG) is required.");
    goto end;
  }

  fsl_xlink_listener( f, fcli.appName, my_xlink_f, NULL );

  opt.user = fsl_cx_user_get(f);

  if(!opt.user){
    rc = fcli_err_set(FSL_RC_MISUSE,
                      "Could not figure out user name to commit as.");
    goto end;
  }
  opt.message = cMsg;
  opt.messageMimeType = cMimeType;
  opt.dumpManifestFile = cDumpMf;
  opt.branch = cBranch;
  opt.bgColor = cColor;

  rc = fsl_db_transaction_begin(db);
  if(rc){
    fsl_cx_uplift_db_error(f, db);
    goto end;
  }
  inTrans = 1;
  fsl_checkout_version_info(f, &ckoutId, NULL);
  rc = fsl_vfile_changes_scan(f, ckoutId, 0);
  if(rc) goto end;

  while((fname = fcli_next_arg(1))){
    const char * gmatch;
    ++fileArgCount;
    gmatch = fsl_cx_glob_matches(f, FSL_GLOBS_IGNORE, fname);
    if(gmatch){
      FCLI_V(("Skipping file due to ignore-glob (%s): %s\n",
              gmatch, fname));
      continue;
    }
    rc = fsl_checkin_file_enqueue( f, fname, 1 );
    if(!rc){
      ++enqueuedArgCount;
      f_out("Queued for commit: %s\n", fname);
    }
    if(rc) goto end;
  }

  /* rc = fsl_checkout_changes_scan(f); */
  /* if(rc) goto end; */

  if(fileArgCount && !enqueuedArgCount){
    rc = fcli_err_set(FSL_RC_NOOP,
                 "All would-be-enqueued files were skipped over due to ignore-glob.");
    goto end;
  }else{
    f_out("vfile selected contents:\n");
    fsl_db_each( fsl_cx_db_checkout(f), stmt_each_dump, NULL,
                 "SELECT vf.id, substr(b.uuid,0,12), vf.pathname "
                 "FROM vfile vf LEFT JOIN blob b "
                 "ON b.rid=vf.rid "
                 "WHERE vf.vid=%"FSL_ID_T_PFMT" "
                 "AND (chnged<>0 OR pathname<>origname)"
                 "AND fsl_is_enqueued(vf.id) "
                 "ORDER BY vf.id", ckoutId);
    f_out("f->ckin.selectedIds count: %d\n",
          (int)f->ckin.selectedIds.entryCount);
  }

  rc = fsl_checkin_commit(f, &opt, &newRid, &newUuid);
  if(rc){
    f_out("rc=%s\n", fsl_rc_cstr(rc));
    goto end;
  }
  f_out("New version: %s (%"FSL_ID_T_PFMT")\n",
        newUuid, (fsl_id_t)newRid);


  if(0){
    f_out("vfile contents:\n");
    fsl_db_each( fsl_cx_db_checkout(f), stmt_each_dump, NULL,
                 "SELECT * from vfile "
                 "WHERE vid=%"FSL_ID_T_PFMT
                 " AND (chnged OR deleted "
                 "      OR (origname IS NOT NULL AND origname<>pathname)"
                 " )"
                 " ORDER BY pathname",
                 (fsl_id_t)newRid );
  }

  if(dryRun){
    f_out("Dry-run mode. Rolling back transaction.\n");
    fsl_db_transaction_rollback(db);
  }else{
    rc = fsl_db_transaction_end(db, 0);
#if 1
    if(!rc) rc = fsl_repo_leaves_rebuild(f)
              /* KLUDGE: as leaves are only checked during commit, we have to do this
                 after the commit :/. */;
#endif
    f_out("Note that libfossil currently has no remote sync support, "
          "so to push your changes you will need to learn the Fossil "
          "sync protocol and speak it to the remote server of your "
          "choice over a telnet connection."
          "\nOr you can just use "
          "fossil(1)'s \"push\" feature.\n");
  }
  inTrans = 0;

  end:
  if(inTrans){
    fsl_db_transaction_rollback(db);
  }
  fsl_free(newUuid);
  return fcli_end_of_main(rc);
}