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