/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* Copyright 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 */ /** This applications implements operations merge operations, like (fossil merge) does. */ #include "libfossil.h" #include /* 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_ { bool flagIntegrate; bool flagCherrypick; bool flagBackout; bool flagDebug; char const * flagBaseline; fsl_merge_opt mopt; struct AppCkout { fsl_id_t rid; fsl_uuid_cstr uuid; } ckout; unsigned int mCounts[FSL_MERGE_FCHANGE_count]; } App = { false,//flagIntegrate false,//flagCherrypick false,//flagBackout false,//flagDebug NULL,//flagBaseline fsl_merge_opt_empty_m, {/*ckout*/0,NULL}, {/*mCounts*/0} }; static char const * fsl__merge_fchange_label(fsl_merge_fchange_e fce){ char const * rc = "???"; switch(fce){ case FSL_MERGE_FCHANGE_ADDED: rc="+"; break; case FSL_MERGE_FCHANGE_COPIED: rc="c"; break; case FSL_MERGE_FCHANGE_RM: rc="-"; break; case FSL_MERGE_FCHANGE_MERGED: rc="m"; break; case FSL_MERGE_FCHANGE_CONFLICT_MERGED: rc="!m"; break; case FSL_MERGE_FCHANGE_CONFLICT_ADDED_UNMANAGED: rc="!+"; break; case FSL_MERGE_FCHANGE_CONFLICT_SYMLINK: rc="!s"; break; case FSL_MERGE_FCHANGE_CONFLICT_BINARY: rc="!b"; break; case FSL_MERGE_FCHANGE_CONFLICT_ANCESTOR: rc="!a"; break; case FSL_MERGE_FCHANGE_RENAMED: rc="r"; break; case FSL_MERGE_FCHANGE_count: case FSL_MERGE_FCHANGE_NONE: rc="!!!"; fsl__fatal(FSL_RC_NYI,"Cannot happen: FSL_MERGE_FCHANGE_NONE"); break; } return rc; } /** App-local fsl_merge_f() callback. */ static int fsl_merge_f_mine(fsl_merge_state const * const ms){ int rc = 0; char const *zLabel = fsl__merge_fchange_label(ms->fileChangeType); ++App.mCounts[ms->fileChangeType]; ++(*((int*)ms->opt->callbackState)); if(ms->priorName){ f_out("[%-3s] %s\n -> %s\n", zLabel, ms->priorName, ms->filename); }else{ f_out("[%-3s] %s\n", zLabel, ms->filename); } return rc; } int main(int argc, const char * const * argv ){ fsl_cx * f = 0; int rc = 0; char const *zMergeSym = 0; const fcli_cliflag FCliFlags[] = { FCLI_FLAG_BOOL(NULL,"integrate",&App.flagIntegrate, "Perform an integrate merge."), FCLI_FLAG_BOOL(NULL,"cherrypick",&App.flagCherrypick, "Perform a cherrypick merge."), FCLI_FLAG_BOOL(NULL,"backout",&App.flagBackout, "Perform a backout (reverse cherrypick) merge."), FCLI_FLAG_BOOL("n", "dry-run", &App.mopt.dryRun, "Enable dry-run mode."), FCLI_FLAG(NULL,"baseline","version",&App.flagBaseline, "Use this version as the \"pivot\" of the merge " "instead of the nearest common ancestor."), fcli_cliflag_empty_m // list MUST end with this (or equivalent) }; const fcli_help_info FCliHelp = { "Merges a repository-side version into the current checkout.", NULL, // very brief usage text, e.g. "file1 [...fileN]" NULL // optional callback which outputs app-specific help }; rc = fcli_setup_v2(argc, argv, FCliFlags, &FCliHelp); if(rc) goto end; f_out("%.60c\n" "WARNING: though libfossil's merge features are believed to\n" "work as expected, they are new, so... YMMV.\n" "Note that libfossil has no undo support!\n" "%.60c\n", '*', '*'); if(App.mopt.dryRun){ f_out("DRY RUN MODE. Use --wet-run to disable dry-run mode.\n"); } while(fcli_flag("debug", NULL)){ ++App.mopt.debug; } if(fcli_has_unused_flags(false)) goto end; f = fcli_cx(); if(!fsl_needs_ckout(f)) goto end; rc = fsl_cx_transaction_begin(f); if(rc) goto end; if(App.flagIntegrate + App.flagCherrypick + App.flagBackout > 1){ rc = fcli_err_set(FSL_RC_MISUSE, "Only one of --integrate, --cherrypick, or " "--backout may be used."); goto end; } fsl_ckout_version_info(f, &App.ckout.rid, &App.ckout.uuid); if(!App.ckout.rid){ rc = fcli_err_set(FSL_RC_MISUSE, "Cannot merge into empty top-level checkin."); goto end; } assert(App.ckout.rid>0 && "libfossil internal API misuse?"); zMergeSym = fcli_next_arg(true); if(!zMergeSym){ rc = fcli_err_set(FSL_RC_MISUSE, "Expecting version to merge from."); goto end; } if((rc=fcli_has_unused_args(false))) goto end; if(App.flagIntegrate) App.mopt.mergeType = FSL_MERGE_TYPE_INTEGRATE; else if(App.flagCherrypick) App.mopt.mergeType = FSL_MERGE_TYPE_CHERRYPICK; else if(App.flagBackout) App.mopt.mergeType = FSL_MERGE_TYPE_BACKOUT; else{ assert(FSL_MERGE_TYPE_NORMAL == App.mopt.mergeType); } rc = fsl_sym_to_rid(f, zMergeSym, FSL_SATYPE_CHECKIN, &App.mopt.mergeRid); symfail: switch(rc){ case 0: break; case FSL_RC_AMBIGUOUS: /* In the libf tree, use zMergSym = c1d7a2 to trigger this. */ fcli_list_ambiguous_artifacts(0, zMergeSym); goto end; default: goto end; } if(App.flagBaseline){ rc = fsl_sym_to_rid(f, App.flagBaseline, FSL_SATYPE_CHECKIN, &App.mopt.baselineRid); if(rc){ zMergeSym = App.flagBaseline; goto symfail; } } f_out("Merging [%s] into [%S]...\n", zMergeSym, App.ckout.uuid); int callbackCount = 0; App.mopt.callback = fsl_merge_f_mine; App.mopt.callbackState = &callbackCount; memset(App.mCounts, 0, sizeof(App.mCounts)); rc = fsl_ckout_merge(f, &App.mopt); while(0==rc && callbackCount){ if(!fcli_is_verbose()){ f_out("Use the --verbose flag to enable a legend of the " "meanings of the cryptic merge status indicators.\n"); break; } struct legend { fsl_merge_fchange_e const fct; char const * altSym; char const *desc; } leg[] = { {FSL_MERGE_FCHANGE_ADDED, 0, "Added"}, {FSL_MERGE_FCHANGE_COPIED, 0, "Copied"}, {FSL_MERGE_FCHANGE_MERGED, 0, "Merged"}, {FSL_MERGE_FCHANGE_RM, 0, "Removed"}, {FSL_MERGE_FCHANGE_RENAMED, 0, "Renamed"}, {FSL_MERGE_FCHANGE_RENAMED, "->", "New name of rename"}, {FSL_MERGE_FCHANGE_CONFLICT_ADDED_UNMANAGED, 0, "Added, overwriting an unmanaged file"}, {FSL_MERGE_FCHANGE_CONFLICT_MERGED, 0, "Merged with conflicts"}, {FSL_MERGE_FCHANGE_CONFLICT_SYMLINK, 0, "Symlink cannot be merged"}, {FSL_MERGE_FCHANGE_CONFLICT_BINARY, 0, "Binary content cannot be merged"}, {FSL_MERGE_FCHANGE_CONFLICT_ANCESTOR, 0, "No common ancestor found"}, {FSL_MERGE_FCHANGE_NONE,NULL,NULL} }; f_out("\nLEGEND:\n"); for(struct legend * l = &leg[0]; l->desc; ++l){ if(App.mCounts[l->fct]){ f_out(" %-3s %s\n", l->altSym ? l->altSym : fsl__merge_fchange_label(l->fct), l->desc); } } f_out("\n"); break; } //f_out("fsl_ckout_merge() rc=%s\n", fsl_rc_cstr(rc)); if(0==rc){ if(App.mopt.dryRun){ f_out("Dry-run mode: rolling back transaction.\n"); } rc = fsl_cx_transaction_end(f, App.mopt.dryRun); } end: if(f && fsl_cx_transaction_level(f)){ f_out("Rolling back transaction.\n"); fsl_cx_transaction_end(f, true); } return fcli_end_of_main(rc); }