/* -*- 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 application implements the libfossil counterpart of (fossil
update).
*/
#include "libfossil.h"
#include <string.h> /*memset()*/
// Only for testing/debugging..
#define MARKER(pfexp) \
do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \
printf pfexp; \
} while(0)
// Global app state.
struct App_ {
int32_t dummp;
} App = {
-1//dummy
};
typedef struct {
fsl_cx * f;
int upCount;
int written;
int kept;
int removed;
bool quiet;
bool overwriteLocalMods;
} extract_state;
/**
A basic fsl_confirm_callback_f() implementation which gets "asked
questions" by the checkout process regarding how to handle certain
situation which could either lead to data loss or extraneous files
lying about.
TODO: connect an interactive and/or flag-driven yes/no/all/cancel
mechanism to this.
*/
static int fsl_confirm_callback_f_my(fsl_confirm_detail const * detail,
fsl_confirm_response *answer,
void * clientState){
extract_state const *state = (extract_state const *)clientState;
if(0 && fcli_is_verbose()){
MARKER(("Asking for confirmation about event #%d: %s\n",
detail->eventId, detail->filename));
}
switch(detail->eventId){
case FSL_CEVENT_RM_MOD_UNMGD_FILE:
// MODIFIED newly-unmanaged files are never removed
answer->response = FSL_CRESPONSE_YES;
break;
case FSL_CEVENT_OVERWRITE_MOD_FILE:
case FSL_CEVENT_OVERWRITE_UNMGD_FILE:
answer->response = state->overwriteLocalMods
? FSL_CRESPONSE_YES//ALWAYS
: FSL_CRESPONSE_NEVER
/* The difference between FSL_CRESPONSE_YES and
FSL_CRESPONSE_ALWAYS is that the latter will cause the
checkout process to keep that answer and stop asking
for purposes of the current phase of the checkout.
*/;
break;
default:
return fsl_cx_err_set(state->f, FSL_RC_UNSUPPORTED,
"Unhanded event ID type %d\n",
detail->eventId);
}
if(0 && fcli_is_verbose()){
MARKER(("eventId %d answer=%d\n", detail->eventId,
answer->response));
}
return 0;
}
/**
fsl_repo_ckout() callback. Called once for each row of
the checkout, including file removals.
*/
static int fsl_ckup_f_my(fsl_ckup_state const *cuState){
enum {DATE_SHORT = 16};
//fsl_repo_extract_state const * xs = cuState->extractState;
extract_state *state = (extract_state *)cuState->callbackState;
char tbuf[DATE_SHORT];
char const * mode = "??";
bool noteworthy = true;
char const * nonTimestamp = 0;
++state->upCount;
switch(cuState->fileChangeType){
case FSL_CKUP_FCHANGE_NONE: mode = " "; noteworthy = false;
++state->kept;
break;
case FSL_CKUP_FCHANGE_ADDED: mode = "+ ";
++state->written;
break;
case FSL_CKUP_FCHANGE_RM: mode = "- ";
++state->removed;
switch(cuState->fileRmInfo){
case FSL_CKUP_RM_KEPT:
++state->kept;
mode = "-U";
noteworthy = true;
nonTimestamp = "<UNMANAGED>";
break;
case FSL_CKUP_RM:
mode = "- ";
assert(cuState->size<0);
noteworthy = true;
nonTimestamp = "<REMOVED> ";
break;
default: break;
}
break;
case FSL_CKUP_FCHANGE_UPDATED: mode = "u ";
++state->written;
break;
case FSL_CKUP_FCHANGE_UPDATED_BINARY: mode = "ub";
++state->written;
break;
case FSL_CKUP_FCHANGE_MERGED: mode = "m ";
++state->written;
break;
case FSL_CKUP_FCHANGE_CONFLICT_MERGED: mode = "m!";
++state->written;
break;
case FSL_CKUP_FCHANGE_CONFLICT_ADDED: mode = "+M";
++state->kept;
break;
case FSL_CKUP_FCHANGE_CONFLICT_ADDED_UNMANAGED: mode = "+!";
++state->written;
break;
case FSL_CKUP_FCHANGE_CONFLICT_RM: mode = "-M";
++state->removed;
++state->kept;
nonTimestamp = "<UNMANAGED>";
break;
case FSL_CKUP_FCHANGE_CONFLICT_SYMLINK: mode = "L!";
++state->written;
break;
case FSL_CKUP_FCHANGE_RENAMED: mode = "n ";
++state->written;
break;
case FSL_CKUP_FCHANGE_EDITED: mode = "e ";
++state->kept;
break;
case FSL_CKUP_FCHANGE_ADD_PROPAGATED: mode = "++";
++state->kept;
break;
case FSL_CKUP_FCHANGE_RM_PROPAGATED: mode = "--";
++state->kept;
break;
case FSL_CKUP_FCHANGE_INVALID:
default:
MARKER(("Unexpected FSL_CKUP_FCHANGE value: #%d\n",
cuState->fileChangeType));
//fsl__fatal(FSL_RC_UNSUPPORTED,
// "Invalid passing-on of FSL_CKUP_FCHANGE_INVALID.");
break;
}
if(noteworthy || !state->quiet){
fsl_strftime_unix(tbuf, DATE_SHORT, "%d %b %Y", cuState->mtime, 1);
f_out("[%s] %8"FSL_INT_T_PFMT" %s %s\n",
mode, cuState->size,
nonTimestamp ? nonTimestamp : tbuf,
cuState->extractState->fCard->name);
}
return 0;
}
int main(int argc, const char * const * argv ){
bool latest = false;
bool forceMissing = false;
bool setMTime = false;
bool fDryRun = false;
char const * sym = NULL;
fsl_id_t tgtRid = 0;
fsl_id_t ckRid = 0;
fsl_cx * f = 0;
extract_state exs;
memset(&exs, 0, sizeof(extract_state));
exs.quiet = true;
/**
Set up flag handling, which is used for processing
basic CLI flags and generating --help text output.
*/
const fcli_cliflag FCliFlags[] = {
// FCLI_FLAG_xxx macros are convenience forms for initializing
// these members...
FCLI_FLAG_BOOL(0,"latest",&latest,
"Pick the most recent version, regardless of branch. "
"Same as passing a version of 'tip'."),
FCLI_FLAG_BOOL(0, "setmtime", &setMTime,
"Set timestamps of all updated files to that "
"of the last check-in in which they were modified "
"(i.e., manifest time)."),
FCLI_FLAG_BOOL(0,"force-missing",&forceMissing,
"Perform update even if some content is missing."),
FCLI_FLAG_BOOL("n", "dry-run", &fDryRun, "Dry-run mode."),
FCLI_FLAG_BOOL_INVERT("Q","not-quiet",&exs.quiet,
"When updating, list all files, not just "
"noteworthy changes."),
FCLI_FLAG("v","version","version",&sym,
"Version to update to. Defaults to the current version of "
"the current branch. May optionally be passed as the first "
"non-flag argument."),
fcli_cliflag_empty_m // list MUST end with this (or equivalent)
};
const fcli_help_info FCliHelp = {
"Updates a fossil checkout to another version, merging any local "
"changes into the updated-to version.",
"[version]", // very brief usage text
NULL // optional callback which outputs app-specific help
};
fcli.cliFlags = FCliFlags;
fcli.appHelp = &FCliHelp;
int rc = fcli_setup(argc, argv);
if(rc) goto end;
f = fcli_cx();
if(!fsl_needs_ckout(f)){
goto end;
}
if(fDryRun){
f_out("Dry-run mode is ON.\n");
}
f_out("********************************************************\n"
"WARNINGS:\n"
" 1) The update API is still in development.\n"
" 2) Use at your own risk.\n"
" 3) There is no UNDO support.\n"
" 4) See #2.\n"
" 5) See #2.\n"
"********************************************************\n\n"
);
if(!sym) sym = fcli_next_arg(true);
if((rc=fcli_has_unused_args(false))) goto end;
fsl_ckout_version_info(f, &ckRid, NULL);
rc = fsl_cx_transaction_begin(f);
if(rc) goto end;
if(sym){
if(0==fsl_strcmp("current", sym)){
/* Same as not passing a version */
//sym = 0;
}else if(0==fsl_strcmp("latest", sym)){
/* Same as passing --latest */
sym = "tip";
latest = true;
}else{
rc = fsl_sym_to_rid(f, sym, FSL_SATYPE_CHECKIN, &tgtRid);
switch(rc){
case 0: break;
case FSL_RC_AMBIGUOUS:
fcli_list_ambiguous_artifacts(NULL,sym);
goto end;
default: goto end;
}
}
}else{
sym = "current";
}
if( !tgtRid ){
rc = latest
? fsl_sym_to_rid(f, "tip", FSL_SATYPE_CHECKIN, &tgtRid)
: fsl_ckout_calc_update_version(f, &tgtRid);
if(rc) goto end;
else if( !tgtRid ) tgtRid = ckRid;
}
if( !tgtRid ){
rc = fcli_err_set(FSL_RC_RANGE,
"Cannot figure out which version "
"to update to. :/");
goto end;
}else if( tgtRid == ckRid ){
f_out("Updating to checkout version. Not sure what good that does, "
"but fossil allows it.\n");
}
f_out("Version to update to: %.12z (RID %" FSL_ID_T_PFMT ")\n",
fsl_rid_to_uuid(f, tgtRid), tgtRid);
fsl_ckup_opt uOpt = fsl_ckup_opt_empty;
fsl_confirmer fcon = fsl_confirmer_empty;
assert(uOpt.scanForChanges);
exs.f = f;
uOpt.dryRun = fDryRun;
uOpt.checkinRid = tgtRid;
uOpt.callback = fsl_ckup_f_my;
uOpt.callbackState = &exs;
uOpt.setMtime = setMTime;
fcon.callback = fsl_confirm_callback_f_my;
fcon.callbackState = &exs;
fsl_cx_confirmer(f, &fcon, NULL);
rc = fsl_ckout_update(f, &uOpt);
if(rc) goto end;
const char * uuid = 0;
fsl_ckout_version_info(f, NULL, &uuid);
f_out("\nProcessed %u file(s) from [%s] [%.16s].\n",
exs.upCount, sym, uuid);
if(exs.written){
f_out("%d SCM'd file(s) written to disk.\n", exs.written);
}
if(exs.kept){
f_out("%d file(s) left unchanged on disk.\n", exs.kept);
}
if(exs.removed){
f_out("%d file(s) removed from checkout.\n", exs.removed);
}
f_out("\n");
fcli_ckout_show_info(false);
if(fDryRun){
f_out("Dry-run mode. Rolling back.\n");
rc = fsl_cx_transaction_end(f, true);
}
end:
if(f && fsl_cx_transaction_level(f)){
int const rc2 =
fsl_cx_transaction_end(f, fDryRun || rc);
if(!rc) rc = rc2;
}
return fcli_end_of_main(rc);
}