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