/* -*-Mode: C++;-*-
* PRCS - The Project Revision Control System
* Copyright (C) 1997 Josh MacDonald
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) an later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id: merge.cc 1.18.1.1.1.26.1.1.1.4.1.9.1.42 Tue, 05 Feb 2002 11:49:08 -0800 jmacd $
*/
extern "C" {
#include <fcntl.h>
}
#include "prcs.h"
#include "hash.h"
#include "diff.h"
#include "populate.h"
#include "checkin.h"
#include "checkout.h"
#include "repository.h"
#include "projdesc.h"
#include "vc.h"
#include "syscmd.h"
#include "setkeys.h"
#include "fileent.h"
#include "misc.h"
#include "system.h"
struct MergeDef {
bool have_working;
bool have_selected;
bool have_common;
bool ws_cmp;
bool wc_cmp;
bool sc_cmp;
int rule_no;
const char *help_string;
};
struct MergeCo {
MergeCo () :working(NULL), selected(NULL), common(NULL) { }
const char* working;
const char* selected;
const char* common;
};
class Mergeable; /* This is an opaque type. It doesn't exist. I'm
* going to cheat on type-correctness with it. */
struct MergeControl {
protected:
ProjectDescriptor* project;
PrVoidError run_merge (Mergeable *,
Mergeable *,
Mergeable *);
virtual omanip<FileTuple> print3 (Mergeable* sel, Mergeable* com, Mergeable* work) = 0;
private:
virtual MergeAction merge_action_by_rule (Mergeable* m, int rule) = 0;
virtual const char* merge_desc (Mergeable* m, int rule) = 0;
virtual PrBoolError quick_cmp (Mergeable* sel, Mergeable* com, Mergeable* work, bool* positive) = 0;
virtual PrVoidError prepare (Mergeable* selected, const char* sel_name,
Mergeable* common, const char* com_name,
Mergeable* working, const char* work_name,
MergeCo* co) = 0;
virtual bool can_add (Mergeable* file) = 0;
virtual bool will_3way (Mergeable* file) = 0;
virtual bool can_view () = 0;
virtual PrVoidError view (Mergeable* sel, Mergeable* com, Mergeable* work, MergeCo* co) = 0;
virtual PrBoolError mergef (Mergeable*, Mergeable*, Mergeable*, MergeCo*) = 0;
virtual PrVoidError addf (Mergeable*, Mergeable*, Mergeable*, MergeCo*) = 0;
virtual PrVoidError deletef (Mergeable*, Mergeable*, Mergeable*, MergeCo*) = 0;
virtual PrVoidError replacef (Mergeable*, Mergeable*, Mergeable*, MergeCo*) = 0;
virtual void notify (Mergeable*, Mergeable*, Mergeable*, MergeAction) = 0;
virtual const char* name (Mergeable*) = 0;
virtual const char* type () = 0;
void merge_announce_os (Mergeable* selected,
Mergeable* common,
Mergeable* working,
ostream &os);
void merge_announce (Mergeable* selected,
Mergeable* common,
Mergeable* working,
MergeAction action);
void merge_action_os (Mergeable* selected,
Mergeable* common,
Mergeable* working,
MergeAction action,
int ruleno,
bool conflict,
ostream &os);
void merge_action (Mergeable* selected,
Mergeable* common,
Mergeable* working,
MergeAction action,
int ruleno,
bool conflict);
const char* file_merge_desc (Mergeable* selected,
Mergeable* common,
Mergeable* working,
int rule);
MergeAction file_merge_action (Mergeable* selected,
Mergeable* common,
Mergeable* working,
int rule);
PrVoidError finish_merge (Mergeable* selected,
Mergeable* common,
Mergeable* working,
const MergeDef* mergedef,
MergeCo *co);
int compact (const MergeDef** arr,
Mergeable* selected,
Mergeable* common,
Mergeable* working,
int len);
void print_merge_help(const char* valid);
};
static const char rule_1_help[] =
"All three files exist and are the same in each version";
static const char rule_2_help[] =
"All three files exist and are different in each version";
static const char rule_3_help[] =
"All three files exist and are alike in the working and common versions";
static const char rule_4_help[] =
"All three files exist and are alike in the selected and common versions";
static const char rule_5_help[] =
"All three files exist and are alike in the selected and working versions";
static const char rule_6_help[] =
"Selected and common files are alike; working file not present";
static const char rule_7_help[] =
"Working and common files are alike; selected file not present";
static const char rule_8_help[] =
"Working and selected files are alike; common file not present";
static const char rule_9_help[] =
"Selected and common files differ; working file not present";
static const char rule_10_help[] =
"Working and common files differ; selected file not present";
static const char rule_11_help[] =
"Working and selected files differ; common file not present";
static const char rule_12_help[] =
"Only the common file is present";
static const char rule_13_help[] =
"Only the working file is present";
static const char rule_14_help[] =
"Only the selected file is present";
static const char rule_2_def_desc[] =
"There are no equivalent files among the three files in each version. "
"It is assumed that you are interested in merging the selected "
"version's changes with your changes. Therefore, the default action is "
"merge";
static const char rule_3_def_desc[] =
"Your version of the file is unchanged from the common version, yet "
"the selected version has been modified. It is assumed then, that the "
"selected version of the file is more up to date, and the default "
"action is to replace your file with the file from the selected "
"version";
static const char rule_6_def_desc[] =
"The file has been deleted from your working version of the project, "
"and still exists in the selected version. The selected version has "
"not been modified, so it is assumed that the file is obsolete";
static const char rule_7_def_desc[] =
"The file has been deleted from the selected of the project, and still "
"exists in your version. You have not modified the file, so your version "
"is assumed to be obsolete. Therefore, the default action is delete";
static const char rule_9_def_desc[] =
"The file has been deleted from your working version of the project, "
"and still exists in the selected version. The selected version has "
"been modified, but it is still assumed that the file is obsolete";
static const char rule_10_def_desc[] =
"The file has been deleted from the selected of the project, and still "
"exists in your version. You have modified the file, so it is not clear "
"what to do. The default action is delete";
static const char rule_11_def_desc[] =
"The file exists in both the selected version and your version, but "
"not in the common version. The files differ. The default action is "
"to merge, with an empty file used as the common version. This is "
"likely to produce serious conflicts";
static const char rule_14_def_desc[] =
"The file exists only in the selected version. Niether the common "
"version nor your working version of the project contain the file. The "
"default action is to add the file";
extern const char *default_merge_descs[14] =
{
NULL,
rule_2_def_desc,
rule_3_def_desc,
NULL,
NULL,
rule_6_def_desc,
rule_7_def_desc,
NULL,
rule_9_def_desc,
rule_10_def_desc,
rule_11_def_desc,
NULL,
NULL,
rule_14_def_desc
};
extern const MergeAction default_merge_actions[14] =
{
/* There is a problem in 1.3.0 reported by Dan Bonachea not really
* having to do with NoPrompt merge actions -- but I'm writing
* this comment anyway. When you do a merge and get noprompt
* because your working file is the same as the selected version
* and then do a partial checkin, excluding that file, you should
* not clobber that version when you checkin to the selected
* branch. Alas, the problem is with partial checkin, not merge.
*/
MergeActionNoPrompt, /* 1 == selected */
MergeActionMerge,
MergeActionReplace,
MergeActionNoPrompt, /* 4 != selected */
MergeActionNoPrompt, /* 5 == selected */
MergeActionNothing,
MergeActionDelete,
MergeActionNoPrompt, /* 8 == selected */
MergeActionNothing,
MergeActionDelete,
MergeActionMerge,
MergeActionNoPrompt, /* 12 no selected */
MergeActionNoPrompt, /* 13 no selected */
MergeActionAdd
};
static const char incomplete_help_string[] =
"Before proceeding, you must decide whether to consider the incompletely "
"merged against version a parent or not. If you do, it will be used later "
"in the search for common ancestors";
#define ARRAY_LEN(x) ((int)(sizeof(x)/sizeof(x[0])))
static const MergeDef merge_defaults[14] =
{
/* W S C WS WC SC */
/* just 1 */
{ false, false, true, false, false, false, 12, rule_12_help },
{ true, false, false, false, false, false, 13, rule_13_help },
{ false, true, false, false, false, false, 14, rule_14_help },
/* same 2 */
{ false, true, true, false, false, false, 6, rule_6_help },
{ true, false, true, false, false, false, 7, rule_7_help },
{ true, true, false, false, false, false, 8, rule_8_help },
/* diff 2 */
{ false, true, true, false, false, true, 9, rule_9_help },
{ true, false, true, false, true, false, 10, rule_10_help },
{ true, true, false, true, false, false, 11, rule_11_help },
/* all */
{ true, true, true, false, false, false, 1, rule_1_help },
{ true, true, true, true, true, true, 2, rule_2_help },
{ true, true, true, true, false, true, 3, rule_3_help },
{ true, true, true, true, true, false, 4, rule_4_help },
{ true, true, true, false, true, true, 5, rule_5_help }
};
#define temp_file_working temp_file_1
#define temp_file_common temp_file_2
#define temp_file_selected temp_file_3
static bool any_obsolete = false;
static CharPtrArray all_conflicts;
static PrVoidError checkout_unkey (FileEntry* selected, const char* sel_name,
FileEntry* common, const char* com_name,
FileEntry* working, const char* work_name,
MergeCo* co);
static PrVoidError run_merge_rename (FileEntry* selected,
FileEntry* common,
FileEntry* working);
static PrVoidError view_merge_diffs (FileEntry*,
FileEntry*,
FileEntry*,
MergeCo*);
static PrVoidError merge_common_files (ProjectDescriptor*,
ProjectDescriptor*,
ProjectDescriptor*);
static PrVoidError merge_projects (ProjectDescriptor*,
ProjectDescriptor*,
ProjectDescriptor*);
static PrVoidError merge_selected_only_files(ProjectDescriptor*,
ProjectDescriptor*,
ProjectDescriptor*);
static PrVoidError merge_working_only_files (ProjectDescriptor*,
ProjectDescriptor*,
ProjectDescriptor*);
static PrBoolError file_quick_cmp (FileEntry* selected,
FileEntry* common,
FileEntry* working,
bool* positive);
static PrBoolError merge_files (ProjectDescriptor*,
FileEntry*,
FileEntry*,
FileEntry*,
MergeCo*);
static PrVoidError replace_file (ProjectDescriptor*,
FileEntry*,
FileEntry*,
FileEntry*,
MergeCo*);
static PrVoidError add_file (ProjectDescriptor*,
FileEntry*,
FileEntry*,
FileEntry*,
MergeCo*);
static PrVoidError delete_file (ProjectDescriptor*,
FileEntry*,
FileEntry*,
FileEntry*,
MergeCo*);
static PrVoidError merge_cleanup_handler (void* data, bool);
static PrProjectDescriptorPtrError checkout_common_version (ProjectVersionData*,
ProjectVersionData*,
ProjectDescriptor*,
RepEntry* rep_entry);
static PrVoidError make_obsolete (FileEntry *fe,
ProjectDescriptor*,
bool force_copy);
static PrBoolError remerge_files (FileEntry *work,
FileEntry *common,
FileEntry *selected);
static bool any_names_differ (FileEntry* working,
FileEntry* common,
FileEntry* selected);
static bool merge_help_is_complete (MergeParentEntryPtrArray* entries,
const char* selected_version);
static PrVoidError merge_help_query_complete (void);
/**********************************************************************/
/* FileEntry merge controller */
/**********************************************************************/
struct FileMergeControl : public MergeControl {
#define CAST(m) ((FileEntry*)m)
public:
FileMergeControl (ProjectDescriptor *proj) { project = proj; }
PrVoidError file_run_merge (FileEntry *sel,
FileEntry *com,
FileEntry *work)
{
return run_merge ((Mergeable*)sel, (Mergeable*)com, (Mergeable*)work);
}
protected:
virtual omanip<FileTuple> print3 (Mergeable* sel, Mergeable* com, Mergeable* work)
{
return omanip<FileTuple>(__omanip_filetuple,
FileTuple(CAST(work), CAST(com), CAST(sel), FileTuple::MergeTriple));
}
private:
virtual MergeAction merge_action_by_rule (Mergeable* m, int rule) {
return CAST(m)->file_attrs()->merge_action(rule - 1);
}
virtual const char* merge_desc (Mergeable* m, int rule)
{
return CAST(m)->file_attrs()->merge_desc(rule - 1);
}
virtual PrBoolError quick_cmp (Mergeable* sel, Mergeable* com, Mergeable* work, bool* positive)
{
return file_quick_cmp (CAST(sel), CAST(com), CAST(work), positive);
}
virtual PrVoidError prepare (Mergeable* selected, const char* sel_name,
Mergeable* common, const char* com_name,
Mergeable* working, const char* work_name,
MergeCo* co)
{
return checkout_unkey (CAST(selected), sel_name,
CAST(common), com_name,
CAST(working), work_name,
co);
}
virtual bool can_add (Mergeable* file)
{
return project->can_add (CAST(file));
}
virtual bool will_3way (Mergeable* file)
{
return CAST(file)->file_type() == RealFile;
}
virtual bool can_view ()
{
return true;
}
virtual PrVoidError view (Mergeable* sel, Mergeable* com, Mergeable* work, MergeCo* co)
{
return view_merge_diffs (CAST(sel), CAST(com), CAST(work), co);
}
virtual PrBoolError mergef (Mergeable* sel, Mergeable* com, Mergeable* work, MergeCo* co)
{
return merge_files (project, CAST(sel), CAST(com), CAST(work), co);
}
virtual PrVoidError addf (Mergeable* sel, Mergeable* com, Mergeable* work, MergeCo* co)
{
return add_file (project, CAST(sel), CAST(com), CAST(work), co);
}
virtual PrVoidError deletef (Mergeable* sel, Mergeable* com, Mergeable* work, MergeCo* co)
{
return delete_file (project, CAST(sel), CAST(com), CAST(work), co);
}
virtual PrVoidError replacef (Mergeable* sel, Mergeable* com, Mergeable* work, MergeCo* co)
{
return replace_file (project, CAST(sel), CAST(com), CAST(work), co);
}
virtual void notify (Mergeable* sel, Mergeable* com, Mergeable* work, MergeAction act)
{
project->merge_notify (CAST(sel), CAST(com), CAST(work), act);
}
virtual const char* name (Mergeable* file)
{
return CAST(file)->working_path();
}
virtual const char* type ()
{
return "file";
}
};
/**********************************************************************/
/* Populate merge controller */
/**********************************************************************/
typedef Pair<const char*, const char*> StringPair;
#undef CAST
#define CAST(m) ((StringPair*)m)
ostream& __omanip_popkey(ostream& o, FileTuple tup)
{
FileEntry* non_null;
if(tup.work_fe)
non_null = tup.work_fe;
else if (tup.sel_fe)
non_null = tup.sel_fe;
else
non_null = tup.com_fe;
o << squote (CAST(non_null)->x());
return o;
}
struct KeyPopMergeControl : public MergeControl {
public:
KeyPopMergeControl (ProjectDescriptor *proj) { project = proj; }
PrVoidError kp_run_merge (StringPair *sel,
StringPair *com,
StringPair *work)
{
return run_merge ((Mergeable*)sel, (Mergeable*)com, (Mergeable*)work);
}
protected:
virtual omanip<FileTuple> print3 (Mergeable* sel, Mergeable* com, Mergeable* work)
{
return omanip<FileTuple>(__omanip_popkey, FileTuple((FileEntry*)sel, (FileEntry*)com, (FileEntry*)work, FileTuple::MergeTriple));
}
private:
virtual MergeAction merge_action_by_rule (Mergeable* , int rule) {
return default_merge_actions[rule-1];
}
virtual const char* merge_desc (Mergeable* , int rule)
{
return default_merge_descs[rule-1];
}
virtual PrBoolError quick_cmp (Mergeable* sel, Mergeable* com, Mergeable* work, bool* positive)
{
*positive = true;
return (sel && com && strcmp (CAST(sel)->y(), CAST(com)->y()) != 0) ||
(sel && work && strcmp (CAST(sel)->y(), CAST(work)->y()) != 0) ||
(com && work && strcmp (CAST(com)->y(), CAST(work)->y()) != 0);
}
virtual PrBoolError mergef (Mergeable* , Mergeable* , Mergeable* , MergeCo*) {abort (); /* NOTREACHED */ return PrBoolError(FatalError); }
virtual bool can_add (Mergeable* ) { return true; }
virtual bool will_3way (Mergeable* ) { return false; }
virtual bool can_view () { return false; }
virtual PrVoidError view (Mergeable* , Mergeable* , Mergeable* , MergeCo* ) { abort (); /* NOTREACHED */ return PrVoidError(FatalError); }
virtual PrVoidError prepare (Mergeable*, const char*, Mergeable*, const char*, Mergeable*, const char*, MergeCo*) {abort (); /* NOTREACHED */ return PrVoidError(FatalError); }
virtual void notify (Mergeable* , Mergeable* , Mergeable* , MergeAction ) { }
virtual const char* name (Mergeable* file) { return CAST(file)->x(); }
};
/**********************************************************************/
/* Populate merge controller */
/**********************************************************************/
struct PopulateMergeControl : public KeyPopMergeControl {
public:
PopulateMergeControl (ProjectDescriptor *proj) :KeyPopMergeControl (proj) { }
private:
virtual PrVoidError addf (Mergeable* sel, Mergeable* , Mergeable* , MergeCo*)
{
ASSERT (sel, "must have a selected file to add");
project->add_populate_ignore (CAST(sel)->x());
return NoError;
}
virtual PrVoidError deletef (Mergeable* , Mergeable* , Mergeable* work, MergeCo*)
{
ASSERT (work, "must have a working file to remove");
project->rem_populate_ignore (CAST(work)->x());
return NoError;
}
/* since these are by name, not value, they will always be the same */
virtual PrVoidError replacef (Mergeable* , Mergeable* , Mergeable* , MergeCo*) { abort(); /* NOTREACHED */ return PrVoidError(FatalError); }
virtual const char* type ()
{
return "populate ignore element";
}
};
/**********************************************************************/
/* Keywords merge controller */
/**********************************************************************/
struct KeywordMergeControl : public KeyPopMergeControl {
public:
KeywordMergeControl (ProjectDescriptor *proj) :KeyPopMergeControl (proj) { }
private:
virtual PrVoidError addf (Mergeable* sel, Mergeable* , Mergeable* , MergeCo*)
{
ASSERT (sel, "must have a selected file to add");
project->add_keyword (CAST(sel)->x(), CAST(sel)->y());
return NoError;
}
virtual PrVoidError deletef (Mergeable* , Mergeable* , Mergeable* work, MergeCo*)
{
ASSERT (work, "must have a working file to remove");
project->rem_keyword (CAST(work)->x());
return NoError;
}
virtual PrVoidError replacef (Mergeable* sel, Mergeable* , Mergeable* , MergeCo*)
{
ASSERT (sel, "must have a selected file to replace");
project->set_keyword (CAST(sel)->x(), CAST(sel)->y());
return NoError;
}
virtual const char* type ()
{
return "project keyword";
}
};
/**********************************************************************/
/* All merge controller decls */
/**********************************************************************/
static FileMergeControl* file_entry_control = NULL;
static PopulateMergeControl* populate_control = NULL;
static KeywordMergeControl* keyword_control = NULL;
/**********************************************************************/
/* Main Command */
/**********************************************************************/
static PrPrcsExitStatusError merge_command2()
{
ProjectDescriptor *working, *selected, *common;
ProjectVersionData *selected_version, *pred_project_data;
RepEntry* rep_entry;
bool cont;
Return_if_fail(working << read_project_file(cmd_root_project_full_name,
cmd_root_project_file_path,
true,
(ProjectReadData)(KeepMergeParents)));
Return_if_fail(working->init_repository_entry(cmd_root_project_name, false, false));
rep_entry = working->repository_entry();
Return_if_fail(working->verify_merge_parents());
pred_project_data = rep_entry->lookup_version(working);
if(!pred_project_data)
pthrow prcserror << "Invalid version in working project file, cannot merge" << dotendl;
if(pred_project_data->deleted ())
pthrow prcserror << "Project version " << pred_project_data << " has been deleted" << dotendl;
eliminate_unnamed_files(working);
Return_if_fail(eliminate_working_files(working, SetNotPresentWarnUser));
working->read_quick_elim();
working->quick_elim_unmodified();
Return_if_fail(selected_version << resolve_version(cmd_version_specifier_major,
cmd_version_specifier_minor,
cmd_root_project_full_name,
cmd_root_project_file_path,
working,
rep_entry));
if(selected_version->prcs_minor_int() == 0) {
Return_if_fail(selected << checkout_empty_prj_file(cmd_root_project_full_name,
selected_version->prcs_major(),
(ProjectReadData)(KeepMergeParents)));
} else {
Return_if_fail(selected << rep_entry->checkout_prj_file(cmd_root_project_full_name,
selected_version->rcs_version(),
(ProjectReadData)(KeepMergeParents)));
}
Return_if_fail(common << checkout_common_version(pred_project_data, selected_version,
working, rep_entry));
if (!common) { /* merge is complete. */
prcsinfo << "Selected version has already been completely merged against"
<< dotendl;
return ExitSuccess;
}
eliminate_unnamed_files(selected);
eliminate_unnamed_files(common);
common->repository_entry(working->repository_entry());
selected->repository_entry(working->repository_entry());
Return_if_fail(warn_unused_files(true));
/* Tell the project file. */
cont = working->merge_continuing (selected_version);
/* Tell the user what's up. */
prcsinfo << "Working version: " << working->full_version() << prcsendl;
prcsinfo << "Common version: " << common->full_version() << prcsendl;
prcsinfo << "Selected version: " << selected->full_version() << prcsendl;
if (cont)
prcsinfo << "Continuing in progress merge" << dotendl;
/* Tell the log what's up */
Return_if_fail(working->init_merge_log());
common->merge_log(working->merge_log());
selected->merge_log(working->merge_log());
working->merge_log() << prcsendl << "*** "
<< (cont ? "Continuuing" : "Beginning")
<< " merge of project "
<< working->project_name() << prcsendl
<< prcsendl
<< "User: " << get_login() << prcsendl
<< "Date: " << get_utc_time() << prcsendl
<< "Common version: " << common->full_version() << prcsendl
<< "Selected version: " << selected->full_version() << prcsendl
<< "Working version: " << working->full_version() << prcsendl;
if (option_skilled_merge)
working->merge_log() << "Option --skilled-merge supplied" << prcsendl;
if (option_force_resolution)
working->merge_log() << "Option --force supplied" << prcsendl;
if (option_report_actions)
working->merge_log() << "Option --no-action supplied" << prcsendl;
working->merge_log() << prcsendl;
foreach (ent_ptr, working->new_merge_parents(), MergeParentEntryPtrArray::ArrayIterator) {
MergeParentEntry *ent = *ent_ptr;
if (ent->state & MergeStateParent)
working->merge_log() << "Working parent: " << (*ent_ptr)->selected_version << prcsendl;
}
/* Do it. */
install_cleanup_handler (merge_cleanup_handler, working);
file_entry_control = new FileMergeControl (working);
keyword_control = new KeywordMergeControl (working);
populate_control = new PopulateMergeControl (working);
Return_if_fail (merge_common_files(selected, common, working));
Return_if_fail (merge_selected_only_files(selected, common, working));
Return_if_fail (merge_working_only_files(selected, common, working));
Return_if_fail (merge_projects(selected, common, working));
working->merge_notify_complete();
for (int i = 0; i < all_conflicts.length (); i += 1) {
prcsoutput << "File " << squote (all_conflicts.index (i)) << " has conflicts which you must edit" << dotendl;
}
/* Note: merged project file is written out by merge_cleanup_handler */
return ExitSuccess;
}
PrPrcsExitStatusError merge_command()
{
PrPrcsExitStatusError ret = merge_command2 ();
if (Failure (ret)) {
if (any_obsolete) {
prcserror << "Merge failed: check `obsolete' subdirectories for saved originals" << dotendl;
}
}
return ret;
}
static PrVoidError merge_cleanup_handler(void* data, bool)
{
if(option_report_actions)
return NoError;
ProjectDescriptor* merged = ((ProjectDescriptor*)data);
merged->merge_finish();
Return_if_fail(merged->write_project_file(cmd_root_project_file_path));
return NoError;
}
static PrProjectDescriptorPtrError checkout_common_version(ProjectVersionData *pred_version_data,
ProjectVersionData *selected_version_data,
ProjectDescriptor *working_project,
RepEntry *rep_entry)
{
MergeParentEntryPtrArray* merge_parents = working_project->new_merge_parents();
Dstring selected_version;
ProjectVersionDataPtrArray *ancestors;
ProjectVersionData working_project_data(-1);
ProjectVersionData *common_version_data = NULL;
selected_version.assign (selected_version_data->prcs_major());
selected_version.append (".");
selected_version.append (selected_version_data->prcs_minor());
working_project_data.prcs_major("no major");
working_project_data.prcs_minor("666");
working_project_data.new_parent(pred_version_data->version_index());
if (merge_parents->length() != 0) {
if (merge_parents->last_index()->state & MergeStateIncomplete &&
strcmp (merge_parents->last_index()->selected_version, selected_version) != 0) {
/* Last merge is incomplete, ask to abort this one and finish last one. */
Return_if_fail (merge_help_query_incomplete(merge_parents->last_index()));
}
if (merge_help_is_complete(merge_parents, selected_version)) {
/* Merge is complete already, if skilled ask to continue, otherwise
* just exit. */
if (option_skilled_merge) {
Return_if_fail (merge_help_query_complete());
} else {
return (ProjectDescriptor*) 0;
}
}
/* Add in any merge parents. */
foreach (ent_ptr, working_project->new_merge_parents(), MergeParentEntryPtrArray::ArrayIterator) {
MergeParentEntry *ent = *ent_ptr;
if (ent->state & MergeStateParent)
working_project_data.new_parent(ent->project_data->version_index());
}
}
ancestors = rep_entry->common_version (&working_project_data, selected_version_data);
if (ancestors->length() == 1) {
common_version_data = ancestors->index(0);
} else if (ancestors->length() == 0) {
prcsquery << "There is no common ancestor, you may use the empty version "
<< pred_version_data->prcs_major() << ".0"
<< ", but you had better know what you are doing. "
<< force ("Aborting")
<< report ("Abort")
<< option ('n', "Continue using empty version")
<< deffail ('y')
<< query ("Abort");
Return_if_fail (prcsquery.result());
return checkout_empty_prj_file(working_project->project_full_name(),
pred_version_data->prcs_major(),
(ProjectReadData)(KeepMergeParents));
} else {
prcsquery << "There is no unique common ancestor between the working project and "
<< selected_version_data << dotendl;
Dstring def_com_ver;
def_com_ver.assign (ancestors->index(0)->prcs_major());
def_com_ver.append (".");
def_com_ver.append (ancestors->index(0)->prcs_minor());
while (!common_version_data) {
prcsinfo << "Possible common ancestors:" << prcsendl;
foreach (ent_ptr, ancestors, ProjectVersionDataPtrArray::ArrayIterator)
prcsinfo << *ent_ptr << prcsendl;
prcsquery << definput (def_com_ver)
<< string_query ("Please select a common ancestor");
const char* result;
Return_if_fail (result << prcsquery.string_result());
const char* result_major = major_version_of (result);
const char* result_minor = minor_version_of (result);
if (!result_major) {
prcsinfo << "Illegal version name" << dotendl;
continue;
}
foreach (ent_ptr, ancestors, ProjectVersionDataPtrArray::ArrayIterator) {
ProjectVersionData *ent = *ent_ptr;
if (strcmp (result_major, ent->prcs_major()) == 0 &&
strcmp (result_minor, ent->prcs_minor()) == 0) {
common_version_data = ent;
break;
}
}
}
}
if (common_version_data->deleted())
pthrow prcserror << "The common ancestor between the working project and "
<< selected_version_data << " has been deleted" << dotendl;
return rep_entry->checkout_prj_file(working_project->project_full_name(),
common_version_data->rcs_version(),
(ProjectReadData)(KeepMergeParents));
}
static bool merge_help_is_complete(MergeParentEntryPtrArray* entries, const char* selected_version)
{
foreach (ent_ptr, entries, MergeParentEntryPtrArray::ArrayIterator) {
MergeParentEntry *entry = *ent_ptr;
if (!(entry->state & MergeStateIncomplete) &&
strcmp (entry->selected_version, selected_version) == 0)
return true;
}
return false;
}
static PrVoidError merge_help_query_complete()
{
prcsquery << "Selected version has already been completely merged against. "
<< force ("Aborting")
<< report ("Abort")
<< option ('y', "Continue anyway, you better know what you're doing")
<< deffail ('n')
<< query ("Continue");
Return_if_fail (prcsquery.result());
return NoError;
}
PrVoidError merge_help_query_incomplete (MergeParentEntry* entry)
{
char c;
prcsquery << "Last merged against version, "
<< entry->project_data
<< ", was not completely merged against. "
<< force("Aborting")
<< report("Abort")
<< help(incomplete_help_string)
<< option('p', "Continue with this merge, consider it a parent")
<< option('c', "Continue with this merge, do not consider it a parent")
<< deffail('n')
<< query("Continue");
Return_if_fail (c << prcsquery.result());
if (c != 'c') {
entry->state = MergeStateIncompleteParent;
} else {
entry->state = MergeStateIncomplete;
}
return NoError;
}
static PrVoidError merge_common_files(ProjectDescriptor* selected,
ProjectDescriptor* common,
ProjectDescriptor* working)
{
FileEntry *common_fe, *selected_fe, *working_fe;
foreach_fileent(fe_ptr, common) {
common_fe = *fe_ptr;
common->repository_entry()->Rep_clear_compressed_cache();
working->repository_entry()->Rep_clear_compressed_cache();
selected->repository_entry()->Rep_clear_compressed_cache();
Return_if_fail (working_fe << working->match_fileent (common_fe));
Return_if_fail (selected_fe << selected->match_fileent (common_fe));
bool was_merged = working->has_been_merged(working_fe, common_fe, selected_fe);
if(was_merged) {
bool remerge;
Return_if_fail (remerge << remerge_files (working_fe, common_fe, selected_fe));
if (!remerge)
continue;
}
if(!was_merged &&
!common_fe->on_command_line() &&
(working_fe && !working_fe->on_command_line()) &&
(selected_fe && !selected_fe->on_command_line())) {
working->merge_notify_incomplete();
continue;
}
Return_if_fail (run_merge_rename (selected_fe, common_fe, working_fe));
}
return NoError;
}
static PrVoidError merge_selected_only_files(ProjectDescriptor* selected,
ProjectDescriptor* common,
ProjectDescriptor* working)
{
FileEntry *selected_fe;
foreach_fileent(fe_ptr, selected) {
selected_fe = *fe_ptr;
common->repository_entry()->Rep_clear_compressed_cache();
working->repository_entry()->Rep_clear_compressed_cache();
selected->repository_entry()->Rep_clear_compressed_cache();
FileEntry *bogus;
Return_if_fail (bogus << common->match_fileent(selected_fe));
if (bogus)
continue;
Return_if_fail (bogus << working->match_fileent(selected_fe));
if (bogus)
continue;
bool was_merged = working->has_been_merged(NULL, NULL, selected_fe);
if(was_merged) {
bool remerge;
Return_if_fail (remerge << remerge_files (NULL, NULL, selected_fe));
if (!remerge)
continue;
}
if(!was_merged && !selected_fe->on_command_line()) {
working->merge_notify_incomplete();
continue;
}
Return_if_fail (run_merge_rename (selected_fe, NULL, NULL));
}
return NoError;
}
static PrVoidError merge_working_only_files(ProjectDescriptor* selected,
ProjectDescriptor* common,
ProjectDescriptor* working)
{
FileEntry *working_fe, *selected_fe;
foreach_fileent(fe_ptr, working) {
working_fe = *fe_ptr;
common->repository_entry()->Rep_clear_compressed_cache();
working->repository_entry()->Rep_clear_compressed_cache();
selected->repository_entry()->Rep_clear_compressed_cache();
FileEntry *bogus;
Return_if_fail (bogus << common->match_fileent(working_fe));
if (bogus)
continue;
Return_if_fail (selected_fe << selected->match_fileent(working_fe));
bool was_merged = working->has_been_merged(working_fe, NULL, selected_fe);
if(was_merged) {
bool remerge;
Return_if_fail (remerge << remerge_files (working_fe, NULL, selected_fe));
if (!remerge)
continue;
}
if(!was_merged &&
!working_fe->on_command_line() &&
(selected_fe && !selected_fe->on_command_line())) {
working->merge_notify_incomplete();
continue;
}
Return_if_fail (run_merge_rename (selected_fe, NULL, working_fe));
}
return NoError;
}
static PrVoidError merge_project_table (KeyPopMergeControl* cont, OrderedStringTable* sel, OrderedStringTable* com, OrderedStringTable* work)
{
OrderedStringTable* tables[3];
tables[0] = sel;
tables[1] = com;
tables[2] = work;
for (int i = 0; i < 3; i += 1) {
if (!tables[i])
continue;
foreach (k_ptr, tables[i]->table(), OrderedStringTable::Table::HashIterator) {
StringPair* k = &(* k_ptr);
bool skip = false;
for (int j = i-1; j >= 0; j -= 1) {
if (! tables[j])
continue;
if (tables[j]->lookup (k->x())) {
skip = true;
break;
}
}
if (skip)
continue;
Return_if_fail (cont->kp_run_merge (tables[0]->table()->lookup_pair (k->x()),
tables[1]->table()->lookup_pair (k->x()),
tables[2]->table()->lookup_pair (k->x())));
}
}
return NoError;
}
static PrVoidError merge_projects (ProjectDescriptor* sel,
ProjectDescriptor* com,
ProjectDescriptor* work)
{
Return_if_fail (merge_project_table (populate_control,
sel->populate_ignore(),
com->populate_ignore(),
work->populate_ignore()));
Return_if_fail (merge_project_table (keyword_control,
sel->project_keywords_extra(),
com->project_keywords_extra(),
work->project_keywords_extra()));
return NoError;
}
static PrBoolError remerge_files (FileEntry *work,
FileEntry *common,
FileEntry *selected)
{
static BangFlag remerge_bang;
if (option_skilled_merge) {
prcsquery << "The file " << merge_tuple(work, common, selected)
<< " has already been merged against the selected version. "
<< force ("Remerging")
<< report ("Remerge")
<< allow_bang (remerge_bang)
<< option ('n', "Ignore this file")
<< defopt ('y', "Remerge this file")
<< query ("Remerge");
char c;
Return_if_fail (c << prcsquery.result());
if (c == 'y')
return true;
}
return false;
}
static bool any_names_differ(FileEntry* working, FileEntry* common, FileEntry* selected)
{
return (working && common && (working->file_type() != common->file_type() ||
strcmp(working->working_path(), common->working_path()) != 0)) ||
(working && selected && (working->file_type() != selected->file_type() ||
strcmp(working->working_path(), selected->working_path()) != 0)) ||
(common && selected && (common->file_type() != selected->file_type() ||
strcmp(common->working_path(), selected->working_path()) != 0));
}
static void print_filetuple(ostream& s, FileTuple tup)
{
bool first = true;
if (tup.work_fe) {
if (tup.type == FileTuple::MergeTriple)
s << "[W: ";
else
s << "[FROM: ";
if (tup.work_fe->file_type() != RealFile)
s << format_type (tup.work_fe->file_type()) << ": ";
s << tup.work_fe->working_path();
first = false;
}
if (tup.com_fe) {
if (first)
s << "[";
else
s << ", ";
if (tup.type == FileTuple::MergeTriple)
s << "C: ";
else
s << "TO: ";
if (tup.com_fe->file_type() != RealFile)
s << format_type (tup.com_fe->file_type()) << ": ";
s << tup.com_fe->working_path();
first = false;
}
if (tup.sel_fe) {
if (first)
s << "[";
else
s << ", ";
if (tup.sel_fe->file_type() != RealFile)
s << format_type (tup.sel_fe->file_type()) << ": ";
s << "S: " << tup.sel_fe->working_path();
}
s << "]";
}
static void output_filetuple (ostream& s, FileTuple tup)
{
if (any_names_differ(tup.work_fe, tup.com_fe, tup.sel_fe)) {
print_filetuple(s, tup);
} else {
FileEntry* non_null;
if(tup.work_fe)
non_null = tup.work_fe;
else if (tup.sel_fe)
non_null = tup.sel_fe;
else
non_null = tup.com_fe;
if (non_null->file_type() != RealFile)
s << "[" << format_type (non_null->file_type()) << ":" << non_null->working_path() << "]";
else
s << squote(non_null->working_path());
}
}
ostream& __omanip_filetuple(ostream& s, FileTuple tup)
{
output_filetuple (s, tup);
return s;
}
/*********************************************************************/
/* ACTION HANDLERS */
/*********************************************************************/
static PrVoidError make_obsolete(FileEntry *fe, ProjectDescriptor *working_project, bool force_copy)
{
if (fe->file_type() == Directory)
return NoError;
if (fe->file_type() == SymLink) {
unlink (fe->working_path());
return NoError;
}
if (!fs_file_exists (fe->working_path()))
return NoError;
const char *work_path = fe->working_path();
const char *lastslash = strrchr(work_path, '/');
int len, path_len, tried = 0;
Dstring obs(work_path);
if(lastslash != NULL)
path_len = len = (lastslash - work_path) + 1;
else
path_len = len = 0;
obs.truncate(len);
obs.append ("obsolete");
len = obs.length();
do {
obs.truncate(len);
if (tried > 0)
obs.append_int(tried++);
else
tried += 1;
} while(fs_file_exists(obs) && !fs_is_directory(obs));
if(!fs_file_exists(obs)) {
If_fail(Err_mkdir(obs, 0777))
pthrow prcserror << "Mkdir failed on directory " << squote(obs) << perror;
}
obs.append('/');
obs.append((const char*)work_path + path_len);
len = obs.length();
tried = 0;
do {
obs.truncate (len);
obs.sprintfa (".v%d", tried++);
} while (fs_file_exists (obs));
if (force_copy)
Return_if_fail (fs_copy_filename (work_path, obs));
else
Return_if_fail (fs_move_filename (work_path, obs));
any_obsolete = true;
if(option_long_format)
prcsinfo << "Copied working file " << squote(work_path)
<< " to " << squote(obs) << prcsendl;
working_project->merge_log() << "Copied working file " << squote(work_path)
<< " to " << squote(obs) << prcsendl;
return NoError;
}
static PrBoolError merge_files(ProjectDescriptor* working_project,
FileEntry* selected,
FileEntry* common,
FileEntry* working,
MergeCo* co)
{
ArgList *args;
Dstring slabel, clabel, wlabel;
int ret;
/* If merging a no_ancestor set of files, common will be null.
*/
ASSERT (!common || common ->file_type() == RealFile, "check first");
ASSERT (selected && selected->file_type() == RealFile, "check first");
ASSERT (working && working ->file_type() == RealFile, "check first");
ASSERT (working->present(), "how could this fail?");
Return_if_fail (checkout_unkey (selected, temp_file_selected,
common, temp_file_common,
working, temp_file_working,
co));
Return_if_fail(working->get_file_sys_info());
Return_if_fail(selected->get_repository_info(selected->project()->repository_entry()));
if (common)
Return_if_fail(common->get_repository_info(common->project()->repository_entry()));
SystemCommand* merge_command = NULL;
if (working->file_attrs()->merge_tool())
merge_command = sys_cmd_by_name(working->file_attrs()->merge_tool());
if (merge_command)
Return_if_fail (args << merge_command->new_arg_list());
else
Return_if_fail (args << gdiff3_command.new_arg_list());
format_diff_label (selected, selected->working_path(), &slabel);
format_diff_label (working, working->working_path(), &wlabel);
if(common)
format_diff_label(common, common->working_path(), &clabel);
else
clabel.sprintf("-L*** empty file ***");
if (!merge_command) {
args->append("-maE");
args->append(wlabel);
args->append(temp_file_working);
args->append(clabel);
args->append(common ? temp_file_common : "/dev/null");
args->append(slabel);
args->append(temp_file_selected);
WriteableFile outfile;
Return_if_fail(outfile.open(working->working_path()));
Return_if_fail(gdiff3_command.open(true, false));
Return_if_fail(outfile.copy(gdiff3_command.standard_out()));
Return_if_fail(ret << gdiff3_command.close());
/* This is in the order it is because if temp_file_working is a
* symlink we can't move working_path() until gdiff finishes.
* simiilarly, we can't overwrite working_path() ... gdiff
* freaks out. */
Return_if_fail(make_obsolete(working, working_project, false));
Return_if_fail(outfile.close());
} else {
args->append(wlabel + 2);
args->append(temp_file_working);
args->append(clabel + 2);
args->append(temp_file_common);
args->append(slabel + 2);
args->append(temp_file_selected);
args->append(working->working_path());
Return_if_fail(make_obsolete(working, working_project, true));
/* @@@ Really, I should set the utime and mode of the files
* here. */
Return_if_fail(ret << merge_command->open_stdout());
}
if (ret > 1) {
pthrow prcserror << (merge_command ? merge_command->path() : gdiff3_command.path())
<< " exited with status " << ret << dotendl;
}
Return_if_fail(working->chmod(working->stat_permissions()));
/* Take the selected version history here, because the working
* version is not always guaranteed to have one. */
working->set (selected);
return (bool)ret;
}
static PrVoidError replace_file(ProjectDescriptor* working_project,
FileEntry* selected,
FileEntry* /* common */,
FileEntry* working,
MergeCo *co)
{
working->set (selected);
if (working->present ()) {
If_fail(working->stat())
pthrow prcserror << "Stat failed on "
<< squote(working->working_path()) << perror;
}
Return_if_fail (make_subdirectories (working->working_path()));
Return_if_fail (make_obsolete (working, working_project, false));
switch (selected->file_type()) {
case RealFile:
Return_if_fail (checkout_unkey (selected, working->working_path(),
NULL, NULL,
NULL, NULL,
co));
if (working->present ())
Return_if_fail (working->chmod(working->stat_permissions()));
else
Return_if_fail (working->chmod(0));
break;
case SymLink:
unlink (working->working_path());
If_fail (Err_symlink (selected->link_name(), working->working_path()));
break;
case Directory:
Return_if_fail (check_create_subdir (working->working_path()));
break;
}
return NoError;
}
static PrVoidError delete_file(ProjectDescriptor* working_project,
FileEntry* /* selected */,
FileEntry* /* common */,
FileEntry* working,
MergeCo* /* co */)
{
Return_if_fail (make_obsolete (working, working_project, false));
Return_if_fail (make_subdirectories (working->working_path()));
working_project->delete_file (working);
return NoError;
}
static PrVoidError add_file(ProjectDescriptor* working_project,
FileEntry* selected,
FileEntry*,
FileEntry*,
MergeCo *co)
{
static bool do_once = true;
if (do_once) {
working_project->append_files_data("\n");
do_once = false;
}
working_project->append_file(selected);
Return_if_fail (make_obsolete (selected, working_project, false));
Return_if_fail (make_subdirectories (selected->working_path()));
switch (selected->file_type()) {
case RealFile:
Return_if_fail (checkout_unkey (selected, selected->working_path(),
NULL, NULL,
NULL, NULL,
co));
Return_if_fail (selected->chmod(0));
break;
case SymLink:
unlink (selected->working_path());
If_fail (Err_symlink (selected->link_name(), selected->working_path()));
break;
case Directory:
Return_if_fail (check_create_subdir (selected->working_path()));
break;
}
return NoError;
}
/*********************************************************************/
/* UTILITIES */
/*********************************************************************/
static PrBoolError file_cmp(const char* file1, const char* file2)
{
int one, two;
If_fail(one << Err_open(file1, O_RDONLY)) {
pthrow prcserror << "Open failed on " << squote(file1) << perror;
}
If_fail(two << Err_open(file2, O_RDONLY)) {
pthrow prcserror << "Open failed on " << squote(file2) << perror;
}
bool diffs;
If_fail(diffs << cmp_fds(one, two)) {
close(one);
close(two);
pthrow prcserror << "Read failed while comparing " << squote(file1)
<< " and " << squote(file2) << perror;
}
close(one);
close(two);
return diffs;
}
static PrVoidError checkout_unkey_work (FileEntry* fe, const char* name)
{
const char* rcsversion, *versionfile, *working_path, *fullpath;
RepEntry *rep_entry = fe->project()->repository_entry();
Return_if_fail(fe->initialize_descriptor(rep_entry, false, false));
Return_if_fail(fe->get_file_sys_info());
rcsversion = fe->descriptor_version_number();
versionfile = fe->full_descriptor_name();
working_path = fe->working_path();
if(fe->keyword_sub()) {
unlink (name);
Return_if_fail( setkeys(working_path,
name,
fe,
Unsetkeys) );
} else {
unlink (name);
Return_if_fail(fullpath << name_in_cwd(working_path));
If_fail (Err_symlink (fullpath, name))
pthrow prcserror << "Failed creating symlink " << squote (name) << perror;
}
return NoError;
}
static PrVoidError checkout_unkey_rep (FileEntry* fe, const char* name)
{
const char* rcsversion, *versionfile;
RepEntry *rep_entry = fe->project()->repository_entry();
Return_if_fail(fe->initialize_descriptor(rep_entry, false, false));
rcsversion = fe->descriptor_version_number();
versionfile = fe->full_descriptor_name();
Return_if_fail(VC_checkout_file(name, rcsversion, versionfile));
return NoError;
}
static PrVoidError checkout_unkey (FileEntry* selected, const char* sel_name,
FileEntry* common, const char* com_name,
FileEntry* working, const char* work_name,
MergeCo* co)
{
if (selected && sel_name && !co->selected) {
Return_if_fail (checkout_unkey_rep (selected, sel_name));
co->selected = sel_name;
}
if (common && com_name && !co->common) {
Return_if_fail (checkout_unkey_rep (common, com_name));
co->common = com_name;
}
if (working && work_name && !co->working) {
if (working->present())
Return_if_fail (checkout_unkey_work (working, work_name));
else
Return_if_fail (checkout_unkey_rep (working, work_name));
co->working = work_name;
}
/* now files are checked out. see if the names are right */
if (sel_name && selected && strcmp (sel_name, co->selected) != 0) {
Return_if_fail (fs_move_filename (co->selected, sel_name));
co->selected = sel_name;
}
if (work_name && working && strcmp (work_name, co->working) != 0) {
Return_if_fail (fs_move_filename (co->working, work_name));
co->working = work_name;
}
if (com_name && common && strcmp (com_name, co->common) != 0) {
Return_if_fail (fs_move_filename (co->common, com_name));
co->common = com_name;
}
return NoError;
}
static PrBoolError file_quick_cmp (FileEntry* selected,
FileEntry* common,
FileEntry* working,
bool* positive)
{
FileEntry* one = selected ? selected : common;
FileEntry* two = working ? working : common;
if (one->file_type() != two->file_type()) {
*positive = true;
return true;
}
if (one->file_type() == SymLink) {
if (two == working) {
const char* lname;
Return_if_fail (lname << read_sym_link (two->working_path()));
two->set_link_name (lname);
}
*positive = true;
return strcmp (one->link_name(), two->link_name()) != 0;
}
if (one->file_type() == Directory) {
*positive = true;
return false;
}
if (working && working->present()) {
if (!working->unmodified() || !working->descriptor_name())
return false; /* value unimportant, positive not set */
}
RcsVersionData* onedat;
RcsVersionData* twodat;
Return_if_fail (onedat << one->project()->repository_entry()->
lookup_rcs_file_data(one->descriptor_name(),
one->descriptor_version_number()));
Return_if_fail (twodat << two->project()->repository_entry()->
lookup_rcs_file_data(two->descriptor_name(),
two->descriptor_version_number()));
*positive = true;
return memcmp (onedat->unkeyed_checksum(), twodat->unkeyed_checksum(), 16) != 0;
}
static PrVoidError maybe_rename (FileEntry* selected,
FileEntry* common,
FileEntry* working)
{
if (selected && working && common &&
strcmp (selected->working_path(), working->working_path()) != 0 &&
strcmp (selected->working_path(), common->working_path()) != 0) {
static BangFlag bang_flag;
prcsquery << "The file " << merge_tuple (working, common, selected)
<< " changes name in the selected version, you may rename the file. "
<< force ("Renaming")
<< report ("Rename")
<< allow_bang (bang_flag)
<< option ('n', "Do not rename this file")
<< defopt ('y', "Rename the file")
<< query ("Select");
char c;
Return_if_fail (c << prcsquery.result());
if (c == 'y' && ! option_report_actions) {
working->project()->merge_log() << "The file " << squote (working->working_path())
<< " was renamed to "
<< squote (selected->working_path()) << prcsendl;
Return_if_fail (make_subdirectories(selected->working_path()));
Return_if_fail (fs_move_filename (working->working_path(), selected->working_path()));
working->set_working_path (selected->working_path());
}
}
return NoError;
}
static PrVoidError run_merge_rename (FileEntry* selected,
FileEntry* common,
FileEntry* working)
{
Return_if_fail (maybe_rename (selected, common, working));
Return_if_fail (file_entry_control->file_run_merge (selected, common, working));
return NoError;
}
static PrVoidError view_merge_diffs(FileEntry* selected,
FileEntry* common,
FileEntry* working,
MergeCo* co)
{
while(true) {
if(selected && common)
prcsquery << option('s', "View common -> selected diffs");
if(working && common)
prcsquery << option('w', "View common -> working diffs");
if(selected && working)
prcsquery << option('d', "View selected -> working diffs");
prcsquery << defopt('l', "Leave this menu") << query("Please select");
char c;
Return_if_fail(c << prcsquery.result());
switch(c) {
case 's':
Return_if_fail (checkout_unkey (selected, temp_file_selected,
common, temp_file_common,
NULL, NULL,
co));
Return_if_fail(diff_pair(common, selected, temp_file_common, temp_file_selected));
break;
case 'w':
Return_if_fail (checkout_unkey (NULL, NULL,
common, temp_file_common,
working, temp_file_working,
co));
Return_if_fail(diff_pair(common, working, temp_file_common, temp_file_working));
break;
case 'd':
Return_if_fail (checkout_unkey (selected, temp_file_selected,
NULL, NULL,
working, temp_file_working,
co));
Return_if_fail(diff_pair(selected, working, temp_file_selected, temp_file_working));
break;
case 'l':
return NoError;
}
}
}
/*********************************************************************/
/* PRINTING AND LOGGING */
/*********************************************************************/
void MergeControl::merge_announce_os (Mergeable* selected,
Mergeable* common,
Mergeable* working,
ostream &os)
{
os << "*** Action on " << type() << " " << print3 (selected, common, working) << prcsendl;
}
void MergeControl::merge_action_os (Mergeable* selected,
Mergeable* common,
Mergeable* working,
MergeAction action,
int ruleno,
bool conflict,
ostream &os)
{
const char* desc;
switch (action) {
case MergeActionMerge:
/* with tool */
desc = "Merge";
break;
case MergeActionAdd:
desc = "Add";
break;
case MergeActionDelete:
desc = "Delete";
break;
case MergeActionNothing:
desc = "Do nothing with";
break;
case MergeActionReplace:
desc = "Replace";
break;
default:
desc = "No prompt for";
break;
}
ASSERT (ruleno != 0, "because no SKIP_FILE");
os << desc << " " << type() << " " << print3 (selected, common, working) << " by rule " << ruleno;
if (conflict)
os << ", conflicts created";
os << prcsendl;
}
void MergeControl::merge_announce (Mergeable* selected,
Mergeable* common,
Mergeable* working,
MergeAction action)
{
merge_announce_os (selected, common, working, project->merge_log());
bool skip_noprompt = (action == MergeActionNoPrompt) && ! option_really_long_format;
bool skip_donothing = (action == MergeActionNothing) && (option_force_resolution || ! option_really_long_format);
if (! skip_noprompt && ! skip_donothing) {
merge_announce_os (selected, common, working, prcsoutput);
}
}
void MergeControl::merge_action (Mergeable* selected,
Mergeable* common,
Mergeable* working,
MergeAction action,
int ruleno,
bool conflict)
{
merge_action_os (selected, common, working, action, ruleno, conflict, project->merge_log());
bool skip_noprompt = (action == MergeActionNoPrompt) && ! option_really_long_format;
bool skip_donothing = (action == MergeActionNothing) && (option_force_resolution || ! option_really_long_format);
if (! skip_noprompt && ! skip_donothing) {
merge_action_os (selected, common, working, action, ruleno, conflict, prcsoutput);
}
project->merge_log() << prcsendl;
}
/*********************************************************************/
/* CONTROLLING LOGIC */
/*********************************************************************/
const char* MergeControl::file_merge_desc (Mergeable* selected,
Mergeable* common,
Mergeable* working,
int rule)
{
Mergeable *non_null;
if (working)
non_null = working;
else if (selected)
non_null = selected;
else {
non_null = common;
ASSERT (non_null, "blah");
}
return merge_desc (non_null, rule);
}
MergeAction MergeControl::file_merge_action (Mergeable* selected,
Mergeable* common,
Mergeable* working,
int rule)
{
Mergeable *non_null;
if (working)
non_null = working;
else if (selected)
non_null = selected;
else {
non_null = common;
ASSERT (non_null, "blah");
}
return merge_action_by_rule (non_null, rule);
}
PrVoidError MergeControl::finish_merge (Mergeable* selected,
Mergeable* common,
Mergeable* working,
const MergeDef* mergedef,
MergeCo *co)
{
MergeAction action = file_merge_action (selected, common, working, mergedef->rule_no);
bool may_add = (working == NULL) && (selected != NULL);
bool may_merge = (working != NULL) && (selected != NULL);
merge_announce (selected, common, working, action);
if (may_add && !can_add (selected)) {
static BangFlag bang_flag;
/* This doesn't happen for KeyPop */
prcsquery << "Normally, you would be prompted to add the " << type() << " "
<< print3 (selected, common, working)
<< ", but it already exists in the working project "
"either by file name or descriptor and therefore cannot be "
"added. "
<< force ("Skipping this file")
<< report ("Skip this file")
<< allow_bang (bang_flag)
<< optfail ('n')
<< defopt ('y', "Do nothing (as if the merge action were 'n')")
<< query ("Skip this file");
Return_if_fail (prcsquery.result());
project->merge_log () << "Cannot add this file due to conflict with currently present file" << prcsendl;
if (action == MergeActionAdd)
action = MergeActionNothing;
may_add = false;
}
if (selected && working &&
(!will_3way (working) ||
!will_3way (selected) ||
(common && !will_3way (common)))) {
if (action == MergeActionMerge) {
action = MergeActionNothing;
project->merge_log () << "Cannot merge this " << type() << ", as it is not a regular file" << prcsendl;
prcsinfo << "Cannot merge non-regular file "
<< print3 (selected, common, working)
<< ", setting default action to nothing" << dotendl;
}
may_merge = false;
}
static bool bang_flags[14];
if (!option_report_actions && !option_force_resolution && !bang_flags[mergedef->rule_no]) {
Dstring valid;
Dstring query;
prcsoutput << "Choose an action on " << type() << " " << print3 (selected, common, working)
<< " for rule " << mergedef->rule_no << ": " << mergedef->help_string << dotendl;
if (may_add)
valid.append ('a');
if (working)
valid.append ('d');
valid.append ('n');
if (may_merge)
valid.append ('m');
if (working && selected) {
valid.append ('r');
}
query.sprintf("Please select(%sh%sq!?)[%c] ", valid.cast(), can_view() ? "v" : "", (char)action);
while(true) {
char c;
re_query_message = query.cast();
re_query_len = query.length();
re_query();
c = get_user_char();
if(c == 0) fprintf(stdout, "\n");
if(c == 0 || c == 'q')
return PrVoidError(UserAbort);
if(c == '?')
print_merge_help(valid);
else if(c == 'h')
prcsoutput << file_merge_desc (selected, common, working, mergedef->rule_no)
<< prcsendl;
else if(can_view() && c == 'v')
Return_if_fail(view (selected, common, working, co));
else if(c == '\n') {
break;
} else if(c == '!') {
bang_flags[mergedef->rule_no] = true;
break;
} else if(strchr(valid, c) != NULL) {
action = (MergeAction)c;
break;
}
}
re_query_message = NULL;
}
bool conflict = false;
if (!option_report_actions) {
switch(action) {
case MergeActionMerge:
Return_if_fail(conflict << mergef (selected, common, working, co));
break;
case MergeActionReplace:
Return_if_fail(replacef (selected, common, working, co));
break;
case MergeActionAdd:
Return_if_fail(addf (selected, common, working, co));
break;
case MergeActionDelete:
Return_if_fail(deletef (selected, common, working, co));
break;
case MergeActionNothing:
break;
default:
ASSERT (false, "notreached");
break;
}
}
merge_action (selected, common, working, action, mergedef->rule_no, conflict);
notify (working, common, selected, action);
if (conflict) {
all_conflicts.append (name (working));
const char* env = get_environ_var ("PRCS_CONFLICT_EDITOR");
if (env) {
SystemCommand* edit_cmd = sys_cmd_by_name (env);
ArgList* args;
Return_if_fail(args << edit_cmd->new_arg_list());
args->append(name (working));
int retval;
Return_if_fail(retval << edit_cmd->open_stdout());
if (retval != 0)
prcswarning << "warning: Conflict editor exited with status "
<< retval << dotendl;
}
}
return NoError;
}
void MergeControl::print_merge_help(const char* valid)
{
prcsoutput << "Valid options are:" << prcsendl;
if (strchr (valid, 'a'))
prcsoutput << "a -- Add selected " << type() << " to working version" << dotendl;
if (strchr (valid, 'd'))
prcsoutput << "d -- Delete working " << type() << dotendl;
prcsoutput << "n -- Do nothing" << dotendl;
if (strchr (valid, 'm'))
prcsoutput << "m -- Merge selected and working " << type() << dotendl;
if (strchr (valid, 'r'))
prcsoutput << "r -- Replace working " << type() << " with selected " << type() << dotendl;
prcsoutput << "h -- Explain this condition" << dotendl
<< "v -- View diffs" << dotendl
<< "q -- " << default_fail_query_message << dotendl
<< "! -- Take the default and do not offer this query again" << dotendl;
}
/* Can skip a rule entirely if this: */
// #define SKIP_FILE(act) ((!option_long_format) &&
// ((act == MergeActionNoPrompt) ||
// ((act == MergeActionNothing) &&
// (!option_force_resolution))))
int MergeControl::compact (const MergeDef** arr,
Mergeable* selected,
Mergeable* common,
Mergeable* working,
int len)
{
int nlen = 0;
for (int i = 0; i < len; i += 1) {
if (arr[i]) { // && !SKIP_FILE(file_merge_action(selected, common, working, arr[i]->rule_no))) {
arr[nlen++] = arr[i];
}
}
return nlen;
}
PrVoidError MergeControl::run_merge (Mergeable* selected,
Mergeable* common,
Mergeable* working)
{
/* Do some analysis to determine how far we can get without
* actually checking out the files. It can be determined
* statically, but that makes it impossible to do anything with
* the no-prompt rules. */
MergeCo co;
const MergeDef *found_defs[5];
int found = 0;
for (int i = 0; i < ARRAY_LEN(merge_defaults); i += 1) {
if ((merge_defaults[i].have_working == (working != NULL)) &&
(merge_defaults[i].have_common == (common != NULL)) &&
(merge_defaults[i].have_selected == (selected != NULL))) {
found_defs[found++] = merge_defaults + i;
}
}
int pass = 0;
found = compact (found_defs, selected, common, working, found);
bool wc_pos = false, ws_pos = false, sc_pos = false;
bool wc_cmp;
bool ws_cmp;
bool sc_cmp;
while (found > 1 || pass == 0) {
if (pass == 0) {
/* All quick comparisons without checking out files. */
if (working && common)
Return_if_fail(wc_cmp << quick_cmp (NULL, common, working, &wc_pos));
if (selected && working)
Return_if_fail(ws_cmp << quick_cmp (selected, NULL, working, &ws_pos));
if (common && selected)
Return_if_fail(sc_cmp << quick_cmp (selected, common, NULL, &sc_pos));
} else if (pass == 1 && selected && common) {
/* Nothing to do here, S-C is always positive */
ASSERT (sc_pos, "not working, must be positive");
} else if (pass == 2 && working && selected && !ws_pos) {
/* The WS comparison */
Return_if_fail (prepare (selected, temp_file_selected,
NULL, NULL,
working, temp_file_working,
&co));
Return_if_fail(ws_cmp << file_cmp (temp_file_selected, temp_file_working));
ws_pos = true;
} else if (pass == 3 && working && common && !wc_pos) {
/* The WC comparison */
Return_if_fail (prepare (NULL, NULL,
common, temp_file_common,
working, temp_file_working,
&co));
Return_if_fail(wc_cmp << file_cmp (temp_file_common, temp_file_working));
wc_pos = true;
} else if (pass == 4) {
ASSERT (false, "this loses badly");
}
for (int i=0; i < found; i += 1) {
if ( (working && common && wc_pos && (wc_cmp != found_defs[i]->wc_cmp)) ||
(working && selected && ws_pos && (ws_cmp != found_defs[i]->ws_cmp)) ||
(selected && common && sc_pos && (sc_cmp != found_defs[i]->sc_cmp)) )
found_defs[i] = NULL;
}
pass += 1;
found = compact (found_defs, selected, common, working, found);
}
ASSERT (found == 1, "no SKIP_FILE");
/* There was a thought that merge should treat NoPrompt actions
* differently by moving this if-statement into finish_merge(),
* see comment by default_merge_actions. */
if (file_merge_action (selected, common, working, found_defs[0]->rule_no) == MergeActionNoPrompt) {
int rule = (found == 0) ? 0 : found_defs[0]->rule_no;
merge_announce (selected, common, working, MergeActionNoPrompt);
merge_action (selected, common, working, MergeActionNoPrompt, rule, false);
return NoError;
}
return finish_merge (selected, common, working, found_defs[0], &co);
}
syntax highlighted by Code2HTML, v. 0.9.1