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