/* -*- 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 the code to checkout a Fossil repository. */ #include "libfossil.h" #include #include #include #define MARKER(pfexp) \ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) typedef struct { fsl_cx * f; int coCount; 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; } static int write_manifest(fsl_cx * f, int m, int mUuid, int mTags){ int wrote = 0; int const rc = fsl_ckout_manifest_write(f, m, mUuid, mTags, &wrote); if(!rc){ if(wrote) f_out("\n"); if(0x001 & wrote) f_out("Wrote manifest.\n"); if(0x010 & wrote) f_out("Wrote manifest.uuid.\n"); if(0x100 & wrote) f_out("Wrote manifest.tags.\n"); } return rc; } /** 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 = false; char const * nonTimestamp = 0; ++state->coCount; switch(cuState->fileChangeType){ case FSL_CKUP_FCHANGE_NONE: mode = " "; ++state->kept; break; case FSL_CKUP_FCHANGE_UPDATED: mode = "w "; ++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 = ""; break; case FSL_CKUP_RM: mode = "- "; assert(cuState->size<0); noteworthy = true; nonTimestamp = " "; break; default: break; } break; default: MARKER(("Unexpected FSL_CKUP_FCHANGE value for ckout op: #%d\n", cuState->fileChangeType)); 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, char const * const *argv) { fsl_db *db = 0; fsl_cx *f = NULL; fsl_id_t prevId = 0, rid = 0; const char *sym = NULL; bool force = false, keep = false, manifest = false, mtime = false, fDryRun = false; int rc = 0; extract_state ex; memset(&ex, 0, sizeof(extract_state)); ex.quiet = true; fcli_cliflag fcli_flags[] = { FCLI_FLAG_BOOL("f", "force", &force, "Continue with the checkout " "irrespective of any unsaved changes in the current checkout."), #if 0 FCLI_FLAG_BOOL(0, "keep", &keep, "Only checkout files from the requested " " that do not have a file of the same name already present on " "disk. Files with the same name as those from the requested will " "remain unmodified irrespective of whether their content is consistent " "with that of the requested . In such a case, the checkout will " "immediately be in a changed state, which 'f-status' will report."), #else FCLI_FLAG_BOOL(0, "keep", &keep, "Modified files in the checkout are retained in " "the new checkout version, rather than being " "overwritten. If a locally-modified file becomes " "unmanaged in the new checkout version, it is kept " "in the filesystem regardless of this flag."), #endif FCLI_FLAG_BOOL(0, "manifest", &manifest, "Only generate the manifest and manifest.uuid files " "for the current checkout version."), FCLI_FLAG_BOOL_INVERT("Q","not-quiet",&ex.quiet, "When checking out, list all files, not just " "noteworthy changes."), FCLI_FLAG_BOOL(0, "setmtime", &mtime, "Set timestamps of all files to that " "of the last check-in in which they were modified " "(i.e., manifest time). FIXME: this does not apply to the " "optional manifest file(s)."), FCLI_FLAG_BOOL("n","dry-run",&fDryRun, "Dry-run mode."), #if 0 FCLI_FLAG_BOOL_INVERT(0,"no-rm", &doRm, "Do not delete files which were removed between the " "original and new checkout versions. By default, only " "locally-unmodified files will be removed."), #endif fcli_cliflag_empty_m }; fcli_help_info fcli_help = { "Change the current checkout to the requested or to the tip of " "the trunk if no is specified.", "[]", NULL }; fcli.cliFlags = fcli_flags; fcli.appHelp = &fcli_help; rc = fcli_setup(argc, argv); if(rc) goto end; f = fcli_cx(); db = fsl_needs_ckout(f); if(!db){ goto end; } if(fcli_has_unused_flags(0)){ goto end; } rc = fsl_cx_transaction_begin(f); if(rc) goto end; fsl_ckout_version_info(f, &prevId, NULL); if(manifest){ if(!prevId){ rc = fcli_err_set(FSL_RC_RANGE,"This checkout is completely empty, " "so cannot generate manifest."); goto end; } assert(prevId>0); rc = write_manifest(f, 1, 1, -1); goto end; } if(prevId>0){ fsl_vfile_changes_scan(f, prevId, mtime ? FSL_VFILE_CKSIG_SETMTIME : 0); } if(!force && fsl_ckout_has_changes(f)) { fcli_err_set(FSL_RC_MISUSE, "The current checkout contains unsaved changes."); goto end; } if(!(sym = fcli_next_arg(1))){ char * mainBranch = fsl_config_get_text(f, FSL_CONFDB_REPO, "main-branch", 0); if(mainBranch){ fcli_fax(mainBranch); sym = mainBranch; }else{ sym = "trunk"; } } rc = fsl_sym_to_rid(f, sym, FSL_SATYPE_CHECKIN, &rid); if(rc) goto end; if(prevId == rid){ f_out("Same version - nothing to do.\n"); goto end; }else{ fsl_confirmer fcon = fsl_confirmer_empty; fcon.callback = fsl_confirm_callback_f_my; fcon.callbackState = &ex; fsl_cx_confirmer(f, &fcon, NULL); } ex.f = f; ex.coCount = ex.written = ex.kept = ex.removed = 0; ex.overwriteLocalMods = !keep; fsl_ckup_opt cOpt = fsl_ckup_opt_empty; cOpt.dryRun = fDryRun; cOpt.scanForChanges = false/*we just did a scan*/; cOpt.checkinRid = rid; cOpt.callback = fsl_ckup_f_my; cOpt.callbackState = &ex; rc = fsl_repo_ckout(f, &cOpt); if(!rc) rc = write_manifest(f, -1, -1, -1); if(rc) goto end; const char * uuid = 0; fsl_ckout_version_info(f, NULL, &uuid); if(ex.written || ex.removed){ f_out("\n"); } f_out("Processed %d file(s) from [%s] [%.16s].\n", ex.coCount, sym, uuid); if(ex.written){ f_out("%d SCM'd file(s) [w]ritten to disk.\n", ex.written); } if(ex.kept){ f_out("%d file(s) left unchanged on disk.\n", ex.kept); } if(ex.removed){ f_out("%d file(s) removed from checkout.\n", ex.removed); } f_out("\n"); fcli_ckout_show_info(false); if(cOpt.dryRun){ f_out("Dry-run mode. Rolling back transaction.\n"); rc = fsl_cx_transaction_end(f, true); } end: if(f && fsl_cx_transaction_level(f)){ int const rc2 = fsl_cx_transaction_end(f, !!rc); rc = rc ? rc : fsl_cx_uplift_db_error2(f, db, rc2); } return fcli_end_of_main(rc); }