/* -*- 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 artifact tagging [test] app using the
libfossil API.
*/
#include "fossil-scm/fossil-cli.h" /* Fossil App mini-framework */
#define MARKER(pfexp) \
do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \
printf pfexp; \
} while(0)
/**
Just experimenting with fsl_xlink_listener() and friends.
*/
static int tag_xlink_f(fsl_deck * d, void * state){
FCLI_V(("Crosslink callback for %s artifact [%.*s] (RID %"FSL_ID_T_PFMT")\n",
fsl_satype_cstr(d->type), 8, d->uuid, d->rid));
return *((char const *)state) /* demonstrate what happens when crosslinking fails. */
? FSL_RC_IO
: 0;
}
static int MarkerA = 0;
static int MarkerT = 0;
static struct App_ {
fsl_list liTgt;
fsl_list liTags;
const char * flagVal;
const char * targetSym;
void * MarkerA;
void * MarkerT;
} App = {
fsl_list_empty_m,
fsl_list_empty_m,
NULL,NULL,&MarkerA,&MarkerT
};
static int fcli_flag_callback_tag(fcli_cliflag const *f){
if(!App.targetSym){
return fcli_err_set(FSL_RC_MISUSE,"The --artifact flag "
"must be specified before --tag.");
}
assert(App.flagVal==*((char const **)f->flagValue));
char * t = 0;
char * v = 0;
char * arg = fsl_strdup(App.flagVal);
int rc = 0;
const char * z = arg;
App.flagVal = 0;
for( ; *z && '='!=*z; ++z){}
if('='==*z){
fcli_fax(arg);
t = fsl_strndup(arg, (fsl_int_t)(z-arg));
if(z[1]) v = fsl_strdup(z+1);
}else{
t = arg;
}
char * tOrig = t;
fsl_tagtype_e ttype = FSL_TAGTYPE_INVALID;
switch(*t){
case '-': ttype = FSL_TAGTYPE_CANCEL; ++t; break;
case '*': ttype = FSL_TAGTYPE_PROPAGATING; ++t; break;
case '+':
++t;
/* fall through */
default:
ttype = FSL_TAGTYPE_ADD;
break;
}
if(0==fsl_strncmp("sym-",t, 4)){
rc = fcli_err_set(FSL_RC_MISUSE,
"Do not set sym-XYZ tags (used for branching) "
"with the --tag flag: they will not be properly "
"processed as sym-tags (namely, the previous "
"branch tag will not be cancelled).");
/* If we ever do allow sym- tags here, they must require
a value. */
}else{
fsl_card_T * tt = fsl_card_T_malloc(ttype, 0, t, v);
fsl_list_append(&App.liTags, tt);
fsl_list_append(&App.liTgt, (void*)App.targetSym);
}
fsl_free(tOrig);
fsl_free(v);
return rc ? rc : FCLI_RC_FLAG_AGAIN;
}
int main(int argc, char const * const * argv ){
int rc = 0;
const char * symToTag = NULL;
char dryRun = 0;
fsl_cx * f = 0;
fsl_db * db = 0;
char failCrosslink = 0;
char const * userName = 0;
fsl_deck mf = fsl_deck_empty;
bool inTrans = 0;
int vbose = 0;
bool testMode = false;
fsl_size_t i;
fcli_pre_setup();
fcli_cliflag FCliFlags[] = {
/* Order is important: -a must come before -t so that args processing
can DTRT. */
FCLI_FLAG("a","artifact","artifact-id-to-tag", &App.targetSym,
"Target artifact ID or symbolic name. "
"Applies to all --tag flags."),
FCLI_FLAG_X("t","tag","name[=value]",&App.flagVal,
fcli_flag_callback_tag,
"Adds the given tag with an optional value. "
"May be used multiple times. "
"Prefix the name with - for a cancellation tag or "
"* for a propagating tag. A prefix of + (add tag) is "
"equivalent to no prefix."
),
FCLI_FLAG_BOOL("D","dry-run",&fcli.clientFlags.dryRun,"Dry-run mode."),
FCLI_FLAG_BOOL(0,"test", &testMode,
"Implies --dry-run and dumps artifact to stdout."),
fcli_cliflag_empty_m
};
fcli_help_info FCliHelp = {
"Creates control (tag) artifacts for a fossil repository.",
"--artifact ID [...other options]",
NULL
};
fcli.cliFlags = FCliFlags;
fcli.appHelp = &FCliHelp;
rc = fcli_setup(argc, argv);
if(FCLI_RC_HELP==rc) /* --help */ return 0;
else if(rc) goto end;
f = fcli.f;
db = fsl_cx_db_repo(f);
if(!db){
rc = fsl_cx_err_set(f, FSL_RC_MISUSE,
"This app requires a repository db.");
goto end;
}
vbose = fcli_is_verbose();
failCrosslink = 0;//fcli_flag2("fx", "fail-xlink", NULL);
fsl_xlink_listener( f, fcli.appName, tag_xlink_f, &failCrosslink );
#if 1
/* LOL: if we short-circuit the -n check based on testMode then
fcli_has_unused_flags() reports -n as unknown/unused. So we
order the booleans in a non-optimal way...
*/
dryRun = fcli_flag("n",NULL) || fcli.clientFlags.dryRun || testMode;
#else
dryRun = 1 /* only while dev'ing */;
#endif
if(fcli_has_unused_flags(0)) goto end;
else if(!App.liTags.used || !App.liTgt.used){
rc = fcli_err_set(FSL_RC_MISUSE, "The --tag and --artifact "
"flags are required. Use --help for more info.");
goto end;
}
userName = fsl_cx_user_get(f) /* set up by fcli */;
if(!userName || !*userName){
rc = fcli_err_set(FSL_RC_NOT_FOUND,
"Could not determine fossil user name. "
"Please specify %sone with --user|-U=name.",
userName ? "a non-empty " : "");
goto end;
}
fsl_deck_init(f, &mf, FSL_SATYPE_CONTROL);
rc = fsl_deck_U_set(&mf, userName);
if(!rc) rc = fsl_deck_D_set(&mf, fsl_julian_now());
if(rc) goto end;
assert(App.liTags.used == App.liTgt.used);
fsl_db_transaction_begin(db);
inTrans = 1;
for( i=0; i<App.liTags.used; ++i ){
fsl_card_T * tc = (fsl_card_T *)App.liTags.list[i];
char const * sym = (char const *)App.liTgt.list[i];
App.liTags.list[i] = NULL;
assert(!tc->uuid);
if(rc){
fsl_card_T_free(tc);
continue;
}
rc = fsl_sym_to_uuid(f, sym, FSL_SATYPE_ANY, &tc->uuid, NULL);
if(!rc) rc = fsl_deck_T_add2(&mf, tc);
if(rc) fsl_card_T_free(tc);
}
if(rc) goto end;
if(testMode){
rc = fsl_deck_unshuffle(&mf, 0);
if(rc) goto end;
rc = fsl_deck_output(&mf, fsl_output_f_FILE, stdout);
if(rc) goto end;
}else{
rc = fsl_deck_save( &mf, 0 );
}
if(!rc && vbose){
f_out("Applied tags to [%s] for user [%s]. New tag: %.12s (RID %"FSL_ID_T_PFMT")\n",
symToTag, userName, mf.uuid, mf.rid);
}
end:
if(rc
&& !fsl_cx_err_get(f, NULL, NULL)
&& mf.error.code ){
fsl_cx_err_set_e(f, &mf.error);
}
if(inTrans){
if(dryRun && vbose) f_out("Dry-run mode. Rolling back transaction.\n");
fsl_db_transaction_end(db, dryRun || rc);
inTrans = 0;
}
fsl_deck_finalize(&mf);
for( i=0; i<App.liTags.used; ++i ){
fsl_card_T_free((fsl_card_T *)App.liTags.list[i]);
}
fsl_list_reserve(&App.liTags, 0);
fsl_list_reserve(&App.liTgt, 0);
return fcli_end_of_main(rc);
}