# # # add_file "tests/resolve_conflict_all_resolutions/conflicts-resolved" # content [4018b395bff3124e1d091e5c600462c88cf8ea62] # # add_file "tests/resolve_conflict_all_resolutions/merge-1" # content [4d660533ce23cbfacdc84e5fed181452a20f8b36] # # add_file "tests/resolve_conflict_all_resolutions/show_first-checkout_left" # content [eea1125a2012131fde7cb392fb3e4900df9bcf26] # # add_file "tests/resolve_conflict_all_resolutions/show_first-checkout_right" # content [7059285f7bb0d9c3f1940949cb9511d8a1c2e5a8] # # add_file "tests/resolve_conflict_all_resolutions/show_first-thermostat" # content [2ee635f6f68eaf75346d6c5ac413c1483e773a10] # # add_file "tests/resolve_conflict_all_resolutions/show_first-user" # content [abab342008177d9c3dd49edfb582f0b9f483003e] # # add_file "tests/resolve_duplicate_name_conflict/merge-1" # content [4894b9f496daee7946ebca36e761710295f9c962] # # patch "cmd_automate.cc" # from [d6b6339f3d9fa5cba017d7316beecf4aa193c9a2] # to [41a77378384b9804621e74d09e5c7f9ad00bb0d8] # # patch "diff_patch.cc" # from [adf43e9fcc6b0a828e7deb3280ad76b5ae1489e4] # to [2836a9e6df47da10d570c35e1bb6b059ea20cac4] # # patch "diff_patch.hh" # from [1fdf49ab99f9d3a6ca15c08f5c5e7828533e5fa2] # to [639f3d8ca629d87c430cd8039336b3e00fbfaea5] # # patch "monotone.texi" # from [4f79edcde5ea9be779fe3651ec9390585bba4612] # to [2ea319de3fbb847eabd1d47a9644e130eae0fe12] # # patch "paths.cc" # from [dde6659e387c8890c96278ea29e3196bdf588139] # to [4ac782ac2313374cd8944cd2296181753ba71f58] # # patch "roster.cc" # from [04ac3afba58ec4c48d56f080d46ed924e51df79c] # to [284a1b73aca418f411ae679fc317ae17cdefa80e] # # patch "roster.hh" # from [c83fc2636e82ac77a1446c42df169b44bb47197f] # to [61c885cf725e7b20bbbe5a8e595904a4edc38c14] # # patch "roster_merge.cc" # from [5c4a8791df3ca572589f2c7e91a565ed8f99dd32] # to [f0ff11778d5de232938b6d2244ab10a0cc5c10d9] # # patch "tests/resolve_conflict_all_resolutions/__driver__.lua" # from [2c013201e50e953df1ca9b55ad726b4c633c9610] # to [eae4fa4b06ac70013d1cabc187778aa0a8f6685b] # # patch "tests/resolve_conflict_all_resolutions/conflicts-1" # from [699148173160c1d22938da045b8bbe737f7b518f] # to [bf24db060f7d4f25c09eff14083d02b9e6a2533b] # # patch "tests/resolve_duplicate_name_conflict/__driver__.lua" # from [320c07fc8141d2eac97f2f6a71e6b9d4754799c9] # to [de6ac22eec67432fccd8a0a2cddac622b639e7c3] # # patch "work.cc" # from [0ef66673796d2a408587d70530944a5a8747d488] # to [815785ca53cb178b928a3e61ba65f03bdeb49fd3] # ============================================================ --- tests/resolve_conflict_all_resolutions/conflicts-resolved 4018b395bff3124e1d091e5c600462c88cf8ea62 +++ tests/resolve_conflict_all_resolutions/conflicts-resolved 4018b395bff3124e1d091e5c600462c88cf8ea62 @@ -0,0 +1,53 @@ + left [deb9772dea922e56623683f9fa58558670cda9a6] + right [670f6a2810983d1f6ce4c6c076f5f685ffd4b17c] +ancestor [59f6060f7ed4dd514c2e0a9bd291ed9a9af29e23] + + conflict duplicate_name + left_type "added file" + left_name "checkout_left.sh" + left_file_id [ae5fe55181c0307c705d0b05fdc1147fc4afd05c] + right_type "added file" + right_name "checkout_left.sh" + right_file_id [355315653eb77ade4804e42a2ef30c89387e1a2d] + resolved_drop_left +resolved_user_right "resolutions/checkout_left.sh" + + conflict duplicate_name + left_type "added file" + left_name "checkout_right.sh" + left_file_id [a2c187e7107dce500e25475fb26ae1e19a18e3e4] + right_type "added file" + right_name "checkout_right.sh" + right_file_id [838cefbcc3a1add031c1d3b407c4ba614bb663bc] + resolved_user_left "resolutions/checkout_right.sh" +resolved_drop_right + + conflict duplicate_name + left_type "added file" + left_name "thermostat.c" + left_file_id [4cdcec6fa2f9d5c075d5b80d03c708c8e4801196] + right_type "added file" + right_name "thermostat.c" + right_file_id [7e9f2712c5d3570815f1546772d9119474d32afc] + resolved_rename_left "thermostat-westinghouse.c" +resolved_rename_right "thermostat-honeywell.c" + + conflict content + node_type "file" + ancestor_name "simple_file" + ancestor_file_id [10c3f68703db3019422c7cb952de8009b66d6ba3] + left_name "simple_file" + left_file_id [5714687dfbef1a2103827321da616343e6ffcbe3] + right_name "simple_file" + right_file_id [b70b9605783c1c4195f490dd0feafc743731478c] +resolved_internal + + conflict content + node_type "file" + ancestor_name "user_file" +ancestor_file_id [25ed8e31b995bb927966616df2a42b979a2717f0] + left_name "user_file" + left_file_id [5ce20f4b9a1da51992d03488cd0be4874324db75] + right_name "user_file" + right_file_id [faa330de9182327a12ef9c7e5eaa0bea7f74ecad] + resolved_user "resolutions/user_file" ============================================================ --- tests/resolve_conflict_all_resolutions/merge-1 4d660533ce23cbfacdc84e5fed181452a20f8b36 +++ tests/resolve_conflict_all_resolutions/merge-1 4d660533ce23cbfacdc84e5fed181452a20f8b36 @@ -0,0 +1,15 @@ +mtn: 2 heads on branch 'testbranch' +mtn: merge 1 / 1: +mtn: calculating best pair of heads to merge next +mtn: [left] 670f6a2810983d1f6ce4c6c076f5f685ffd4b17c +mtn: [right] deb9772dea922e56623683f9fa58558670cda9a6 +mtn: dropping checkout_left.sh +mtn: replacing content of checkout_left.sh with resolutions/checkout_left.sh +mtn: replacing content of checkout_right.sh with resolutions/checkout_right.sh +mtn: dropping checkout_right.sh +mtn: renaming thermostat.c to thermostat-westinghouse.c +mtn: renaming thermostat.c to thermostat-honeywell.c +mtn: merged simple_file, simple_file +mtn: replacing content of user_file, user_file with resolutions/user_file +mtn: [merged] 1280be5b732ba73dc73a63c5e6a77efc4f690d90 +mtn: note: your workspaces have not been updated ============================================================ --- tests/resolve_conflict_all_resolutions/show_first-checkout_left eea1125a2012131fde7cb392fb3e4900df9bcf26 +++ tests/resolve_conflict_all_resolutions/show_first-checkout_left eea1125a2012131fde7cb392fb3e4900df9bcf26 @@ -0,0 +1,8 @@ +mtn: duplicate_name checkout_left.sh +mtn: possible resolutions: +mtn: resolve_first_left drop +mtn: resolve_first_left rename "name" +mtn: resolve_first_left user "name" +mtn: resolve_first_right drop +mtn: resolve_first_right rename "name" +mtn: resolve_first_right user "name" ============================================================ --- tests/resolve_conflict_all_resolutions/show_first-checkout_right 7059285f7bb0d9c3f1940949cb9511d8a1c2e5a8 +++ tests/resolve_conflict_all_resolutions/show_first-checkout_right 7059285f7bb0d9c3f1940949cb9511d8a1c2e5a8 @@ -0,0 +1,8 @@ +mtn: duplicate_name checkout_right.sh +mtn: possible resolutions: +mtn: resolve_first_left drop +mtn: resolve_first_left rename "name" +mtn: resolve_first_left user "name" +mtn: resolve_first_right drop +mtn: resolve_first_right rename "name" +mtn: resolve_first_right user "name" ============================================================ --- tests/resolve_conflict_all_resolutions/show_first-thermostat 2ee635f6f68eaf75346d6c5ac413c1483e773a10 +++ tests/resolve_conflict_all_resolutions/show_first-thermostat 2ee635f6f68eaf75346d6c5ac413c1483e773a10 @@ -0,0 +1,8 @@ +mtn: duplicate_name thermostat.c +mtn: possible resolutions: +mtn: resolve_first_left drop +mtn: resolve_first_left rename "name" +mtn: resolve_first_left user "name" +mtn: resolve_first_right drop +mtn: resolve_first_right rename "name" +mtn: resolve_first_right user "name" ============================================================ --- tests/resolve_conflict_all_resolutions/show_first-user abab342008177d9c3dd49edfb582f0b9f483003e +++ tests/resolve_conflict_all_resolutions/show_first-user abab342008177d9c3dd49edfb582f0b9f483003e @@ -0,0 +1,3 @@ +mtn: content user_file +mtn: possible resolutions: +mtn: resolve user "file_name" ============================================================ --- tests/resolve_duplicate_name_conflict/merge-1 4894b9f496daee7946ebca36e761710295f9c962 +++ tests/resolve_duplicate_name_conflict/merge-1 4894b9f496daee7946ebca36e761710295f9c962 @@ -0,0 +1,11 @@ +mtn: 2 heads on branch 'testbranch' +mtn: merge 1 / 1: +mtn: calculating best pair of heads to merge next +mtn: [left] 1337cb1059c4bc3e376b14381b43e9383c654da1 +mtn: [right] d5f1dd136c86b5bbd5e71b0c3365667e328af492 +mtn: dropping checkout.sh +mtn: replacing content of checkout.sh with _MTN/resolutions/checkout.sh +mtn: renaming thermostat.c to 0x4811d78 +mtn: renaming thermostat.c to 0x4862598 +mtn: [merged] 14964233a7361ada1ba962f7863d4fee12f30df7 +mtn: note: your workspaces have not been updated ============================================================ --- cmd_automate.cc d6b6339f3d9fa5cba017d7316beecf4aa193c9a2 +++ cmd_automate.cc 41a77378384b9804621e74d09e5c7f9ad00bb0d8 @@ -68,7 +68,7 @@ namespace commands { } } -static string const interface_version = "8.0"; +static string const interface_version = "8.1"; // Major or minor number only increments once for each monotone release; // check the most recent release before incrementing this. ============================================================ --- diff_patch.cc adf43e9fcc6b0a828e7deb3280ad76b5ae1489e4 +++ diff_patch.cc 2836a9e6df47da10d570c35e1bb6b059ea20cac4 @@ -535,6 +535,27 @@ void } void +content_merge_database_adaptor::record_file(file_id const & parent_ident, + file_id const & merged_ident, + file_data const & parent_data, + file_data const & merged_data) +{ + L(FL("recording file %s -> %s") + % parent_ident + % merged_ident); + + transaction_guard guard(db); + + if (!(parent_ident == merged_ident)) + { + delta parent_delta; + diff(parent_data.inner(), merged_data.inner(), parent_delta); + db.put_file_version(parent_ident, merged_ident, file_delta(parent_delta)); + } + guard.commit(); +} + +void content_merge_database_adaptor::cache_roster(revision_id const & rid, boost::shared_ptr roster) { @@ -641,6 +662,21 @@ void } void +content_merge_workspace_adaptor::record_file(file_id const & parent_id, + file_id const & merged_id, + file_data const & parent_data, + file_data const & merged_data) +{ + L(FL("temporarily recording file %s -> %s") + % parent_id + % merged_id); + // this is an insert instead of a safe_insert because it is perfectly + // legal (though rare) to have multiple merges resolve to the same file + // contents. + temporary_store.insert(make_pair(merged_id, merged_data)); +} + +void content_merge_workspace_adaptor::get_ancestral_roster(node_id nid, revision_id & rid, shared_ptr & anc) @@ -727,6 +763,15 @@ void } void +content_merge_checkout_adaptor::record_file(file_id const & parent_ident, + file_id const & merged_ident, + file_data const & parent_data, + file_data const & merged_data) +{ + I(false); +} + +void content_merge_checkout_adaptor::get_ancestral_roster(node_id nid, revision_id & rid, shared_ptr & anc) ============================================================ --- diff_patch.hh 1fdf49ab99f9d3a6ca15c08f5c5e7828533e5fa2 +++ diff_patch.hh 639f3d8ca629d87c430cd8039336b3e00fbfaea5 @@ -49,6 +49,12 @@ content_merge_adaptor file_data const & right_data, file_data const & merged_data) = 0; + // For use when one side of the merge is dropped + virtual void record_file(file_id const & parent_ident, + file_id const & merged_ident, + file_data const & parent_data, + file_data const & merged_data) = 0; + virtual void get_ancestral_roster(node_id nid, revision_id & rid, boost::shared_ptr & anc) = 0; @@ -82,6 +88,11 @@ content_merge_database_adaptor file_data const & right_data, file_data const & merged_data); + void record_file(file_id const & parent_ident, + file_id const & merged_ident, + file_data const & parent_data, + file_data const & merged_data); + void cache_roster(revision_id const & rid, boost::shared_ptr roster); @@ -125,6 +136,11 @@ content_merge_workspace_adaptor file_data const & right_data, file_data const & merged_data); + void record_file(file_id const & parent_ident, + file_id const & merged_ident, + file_data const & parent_data, + file_data const & merged_data); + void get_ancestral_roster(node_id nid, revision_id & rid, boost::shared_ptr & anc); @@ -149,6 +165,11 @@ content_merge_checkout_adaptor file_data const & right_data, file_data const & merged_data); + void record_file(file_id const & parent_ident, + file_id const & merged_ident, + file_data const & parent_data, + file_data const & merged_data); + void get_ancestral_roster(node_id nid, revision_id & rid, boost::shared_ptr & anc); ============================================================ --- monotone.texi 4f79edcde5ea9be779fe3651ec9390585bba4612 +++ monotone.texi 2ea319de3fbb847eabd1d47a9644e130eae0fe12 @@ -2170,7 +2170,8 @@ @section Dealing with a Fork revision. This happened automatically, because the changes between the common ancestor and heads did not conflict. If there had been a conflict, monotone would have invoked an external merging tool to help -resolve it. +resolve it, or Jim could have used the @command{conflicts} set of +commands to resolve it (@pxref{Conflicts}). After merging, the branch has a single head again, and Jim updates his workspace. @@ -3019,30 +3020,27 @@ @section Merge Conflicts Unfortunately, these commands can't yet list conflicts between a database revision and the current workspace. -All merging commands accept options that specify conflict resolutions: address@hidden @command address@hidden address@hidden resolution} address@hidden address@hidden address@hidden table +In addition, the @command{conflicts} set of commands can be used to +specify resolutions for some conflicts. The resolutions are stored in a +file, and given to the @command{merge} command via the address@hidden option; see @xref{Conflicts}. -For @command{--resolve-conflicts-file}, the file must contain the output -of @command{automate show_conflicts}, with conflict resolutions -appended to each stanza. @file{_MTN/conflicts} is a good place to put -the file. - -For @command{--resolve-conflicts}, a single conflict resolution is given -in the string; it has the same format as the conflict resolutions in -the file, and must be applicable to all conflicts in the merge. This -is most usefull when there is a single conflict. - The @command{merge} command normally will perform as many merges as necessary to merge all current heads of a branch. However, when address@hidden or @command{--resolve-conflicts-file} is given, -the conflicts and their resolutions apply only to the first merge, so -the subsequent merges are not done; the @command{merge} command must -be repeated, possibly with new conflicts and resolutions, to merge the -remaining heads. address@hidden is given, the conflicts and their +resolutions apply only to the first merge, so the subsequent merges +are not done; the @command{merge} command must be repeated, possibly +with new conflicts and resolutions, to merge the remaining heads. +If @command{conflicts} supports resolving a particular conflict, that +is the simplest way to resolve it. Otherwise, resolving the different +types of conflicts is accomplished by checking out one of the +conflicting revisions, making changes as described below, committing +these changes as a new revision and then running the merge again using +this new revision as one of the merge parents. This process can be +repeated as necessary to get two revisions into a state where they +will merge cleanly, or with a minimum of file content conflicts. + The possible conflict resolutions are discussed with each conflict in the following sections. @@ -3065,8 +3063,9 @@ @subheading File Content Conflict Monotone does not generally use CVS style conflict markers for content conflicts. Instead it makes the content of both conflicting files and -the content of their common ancestor available for use with your -favorite merge tool. See the @code{merge3} hook for more information. +the content of their common ancestor available for interactive use +during the merge with your favorite merge tool. See the @code{merge3} +hook for more information. Alternatively, rather than using a merge tool it is possible to make further changes to one or both of the conflicting file versions so @@ -3076,6 +3075,13 @@ @subheading File Content Conflict such conflicting changes with a merge tool, similar rearrangements can be made to one of the conflicting files before redoing the merge. +Finally, you can use your favorite merge tool asychronously with the +merge, and specify the result file in the conflicts file, using the address@hidden command (@pxref{Conflicts}): address@hidden +mtn conflicts resolve_first user filename address@hidden smallexample + @subheading Duplicate Name Conflict A duplicate name conflict occurs when two distinct files or @@ -3107,13 +3113,13 @@ @subsubheading Same file revisions containing the new files. @subsubheading Same file -For the first case, the conflict is resolved by @command{drop}ing one -file. The contents should be manually merged first, in case they are -slightly different. Typically, a user will have one of the files in -their current workspace; the other can be retrieved via address@hidden get_file_of}; the revision id is shown in the merge -error message. The process can be confusing; here's a detailed -example. +For the first case, the conflict is resolved by dropping one file, +using @command{conflicts} commands. The contents should be manually +merged, in case they are slightly different. Typically, a user will +have one of the files in their current workspace; the other can be +retrieved via @command{automate get_file_of}; the revision id is shown +in the merge error message. The process can be confusing; here's a +detailed example. Suppose Beth and Abe each commit a new file @file{checkout.sh}. When Beth attempts to merge the two heads, she gets a message like: @@ -3131,46 +3137,38 @@ @subsubheading Same file @end smallexample The file labeled @code{right} is the file in Beth's workspace. To -retrieve a copy of Abe's file, Beth executes: +start the conflict resolution process, Beth first saves the list of +conflicts: @smallexample address@hidden -mtn automate get_file_of checkout.sh \ ---revision=ae94e6677b8e31692c67d98744dccf5fa9ccffe5 \ -> checkout.sh-merge address@hidden group +mtn conflicts store @end smallexample -Now Beth manually merges (using her favorite merge tool) address@hidden and @file{checkout.sh-merge}, leaving the results in address@hidden (@emph{not} in her copy). +In order to merge Beth's and Abe's file versions, Beth retrieves a +copy of Abe's file: -Then Beth drops her copy, and commits that change: - @smallexample @group -mtn drop checkout.sh -mtn commit --message "resolving conflicting 'checkout.sh'" +mtn automate get_file_of checkout.sh \ +--revision=ae94e6677b8e31692c67d98744dccf5fa9ccffe5 \ +> _MTN/resolutions/checkout.sh-abe @end group @end smallexample -Now Beth can merge the two heads, and update her workspace: +Now Beth manually merges (using her favorite merge tool) address@hidden and @file{_MTN/resolutions/checkout.sh-abe}, +leaving the results in @file{_MTN/resolutions/checkout.sh-merge} +(@emph{not} in her copy). address@hidden address@hidden -mtn merge -mtn update address@hidden group address@hidden smallexample +Then Beth specifies the conflict resolution, and finishes the merge: -This leaves a copy of Abe's original @file{checkout.sh}. Beth -overwrites it with her merged version, and commits again: - @smallexample @group -rm checkout.sh -mv checkout.sh-merge checkout.sh -mtn commit --message "resolving conflicting 'checkout.sh' - done" +mtn conflicts resolve_first_left drop +mtn conflicts resolve_first_right user _MTN/resolutions/checkout.sh-merge +mtn merge --resolve-conflicts-file=_MTN/conflicts +mtn conflicts clean +mtn update @end group @end smallexample @@ -3189,19 +3187,18 @@ @subsubheading Different files @smallexample @group -mtn rename thermostat thermostat-honeywell -mtn merge +mtn conflicts store +mtn conflicts resolve_first_left rename thermostat-westinghouse +mtn conflicts resolve_first_right rename thermostat-honeywell +mtn merge --resolve-conflicts-file=_MTN/conflicts +mtn conflicts clean mtn update @end group @end smallexample Now she has her file in @file{thermostat-honeywell}, and Abe's in address@hidden She may rename Abe's file to address@hidden, or ask Abe to do that. address@hidden -If a project has a good file naming convention, this second case -should be rare. - @subheading Missing Root Conflict Monotone's merge strategy is sometimes referred to as @@ -3229,6 +3226,8 @@ @subheading Missing Root Conflict See the @command{pivot_root} command for more information on moving another directory to the project root. address@hidden does not yet support resolving this conflict. + @subheading Invalid Name Conflict Monotone reserves the name @file{_MTN} in a workspace root directory @@ -3255,6 +3254,8 @@ @subheading Invalid Name Conflict See the @command{pivot_root} command for more information on moving another directory to the project root. address@hidden does not yet support resolving this conflict. + @subheading Directory Loop Conflict A directory loop conflict occurs when one directory is moved under a @@ -3264,6 +3265,8 @@ @subheading Directory Loop Conflict Directory loop conflicts should be rare but can be easily resolved by moving one of the conflicting directories out from under the other. address@hidden does not yet support resolving this conflict. + @subheading Orphaned Node Conflict An orphaned node conflict occurs when a directory and all of its @@ -3277,6 +3280,8 @@ @subheading Orphaned Node Conflict in both merge parents, or that has been added in the revision containing the orphaned files or directories. address@hidden does not yet support resolving this conflict. + @subheading Multiple Name Conflict A multiple name conflict occurs when a single file or directory has @@ -3288,6 +3293,8 @@ @subheading Multiple Name Conflict resolved by renaming the conflicting file or directory in one or both of the merge parents so that both agree on the name. address@hidden does not yet support resolving this conflict. + In earlier versions of monotone (those before version 0.39) this type of conflict was referred to as a @emph{name conflict}. @@ -3304,16 +3311,8 @@ @subheading Attribute Conflict the die-die-die rules and may be resurrected by simply setting their values. address@hidden Conflict Resolution address@hidden does not yet support resolving this conflict. -Resolving the different types of conflicts is accomplished by checking -out one of the conflicting revisions, making changes as described -above, committing these changes as a new revision and then running the -merge again using this new revision as one of the merge parents. This -process can be repeated as necessary to get two revisions into a state -where they will merge cleanly, or with a minimum of file content -conflicts. - @page @node Workspace Collisions, Quality Assurance, Merge Conflicts, Advanced Uses @section Workspace Collisions @@ -4650,8 +4649,97 @@ @section Tree null revision. @end ftable address@hidden address@hidden +* Conflicts:: address@hidden menu address@hidden Conflicts, , Tree, Tree address@hidden Conflicts +The @command{conflicts} set of commands is used to specify conflict +resolutions for merges, asynchronously from the merge command itself. +This lets the user take as much time as needed to prepare all the +conflict resolutions, and avoids losing work when a merge is aborted +due to a complicated conflict. +For all of these commands, if the @command{--conflicts-file} option is +not given, the file @file{_MTN/conflicts} is used. + +Except for @command{conflicts clean}, these commands do not require a +workspace, unless the default conflicts file is used. + address@hidden @command address@hidden mtn conflicts address@hidden store @var{left_rev_id} @var{right_rev_id} +Store the conflicts encountered by merging @var{left_rev_id} with address@hidden, in the specified file. + +If @var{left_rev_id} and @var{right_rev_id} are not given, the first +two heads that the @command{merge} command would merge are used. + +The conflicts file format is as output by the @command{automate +show_conflicts} command; see @xref{Automation}. + +Content conflicts that can be resolved by the internal line merger +have resolutions, so they will not show up in subsequent address@hidden commands. + address@hidden mtn conflicts address@hidden show_first +Show the first unresolved conflict in the conflicts file, and list the +possible resolutions. + address@hidden mtn conflicts address@hidden resolve_first @var{resolution} +Specify a resolution for the first conflict in the conflicts file; it +must be a single file conflict. The conflicts file is updated. + address@hidden mtn conflicts address@hidden resolve_first_left @var{resolution} address@hidden mtn conflicts address@hidden resolve_first_right @var{resolution} +Specify a resolution for one of the files in the first conflict in the +conflicts file; it must be a two file conflict. The conflicts file is +updated. + address@hidden mtn conflicts clean +Delete the default conflicts file @file{_MTN/conflicts}, and the +directory @file{_MTN/resolutions}. Users may store conflict +resolution files in @file{_MTN/resolutions}; this command provides a +convenient way to clean up. address@hidden ftable + +For single file conflicts, the only possible resolution is: + address@hidden @command address@hidden user @var{file} +The file contents are replaced by the contents of the specified file. + +This inserts a @var{resolved_user file} conflict resolution in the +conflicts file. address@hidden ftable + +For two file conflicts, the possible resolutions are: + address@hidden @command address@hidden drop +The file is dropped in the merge. + +This inserts a @var{resolved_drop_left} or @var{resolved_drop_right} +conflict resolution in the conflicts file. + address@hidden rename @var{filename} +The file is renamed. + +This inserts a @var{resolved_rename_left filename} or address@hidden filename} conflict resolution in the +conflicts file. + address@hidden user @var{file} +The file contents are replaced by the contents of the specified file. +The other file in the conflict must be dropped or renamed. + +This inserts a @var{resolved_user_left file} or address@hidden file} conflict resolution in the conflicts +file. + address@hidden ftable + @page @node Workspace, Network, Tree, Command Reference @section Workspace @@ -8680,7 +8768,8 @@ @section Automation @itemize @item -FIXME_RESOLVE_CONFLICTS: -- added default resolution for file content conflicts +8.1: -- added default resolution for file content conflicts, user +resolutions for others. @item 7.1 -- initial @end itemize @@ -8693,6 +8782,11 @@ @section Automation can guide the user thru resolving each conflict in turn, then do the merge. +The same file format is output by the @command{conflicts store} +command, which also allows specifying user conflict resolutions. The +file syntax for the resolutions is given here, so an external tool can +set them directly. + For more information on conflicts, @ref{Merge Conflicts}. Note that this cannot be used to show conflicts that would occur in an @@ -8826,10 +8920,9 @@ @section Automation right_name "baz" right_file_id [b966b2d35b99e456cb0c55e4573ef0b1b155b4a9] @end verbatim address@hidden is a conflict resolution; see @ref{Merge -Conflicts}. If the file contents in the two revs can be successfully -merged by the internal line merger, @code{resolved_internal} is -output. address@hidden is a conflict resolution. If the file +contents in the two revs can be successfully merged by the internal +line merger, @code{resolved_internal} is output. File added and/or renamed: @verbatim @@ -8912,6 +9005,20 @@ @section Automation right_name "" @end verbatim +All possible conflict resolutions: address@hidden + resolved_drop_left + resolved_drop_right + resolved_internal + resolved_rename_left file +resolved_rename_right file + resolved_user file + resolved_user_left file + resolved_user_right file address@hidden verbatim + address@hidden, for more information on conflict resolutions. + @item Output format: First the revision ids of the left and right revisions, and their @@ -8929,6 +9036,11 @@ @section Automation contents can be retrieved efficiently via @code{automate get_file}, to aid in conflict resolution. +Only the @command{resolved_internal} conflict resolution is output by +this command; the other conflict resolutions are inserted in a +conflicts file by @command{conflicts resolve_first} or an external +tool, and read by @command{merge}. + @item Error conditions: If the revision IDs are given, but either is unknown or ============================================================ --- paths.cc dde6659e387c8890c96278ea29e3196bdf588139 +++ paths.cc 4ac782ac2313374cd8944cd2296181753ba71f58 @@ -483,7 +483,14 @@ bookkeeping_path::external_string_is_boo { // FIXME: this charset casting everywhere is ridiculous string normalized; - normalize_external_path(path(), normalized); + try + { + normalize_external_path(path(), normalized); + } + catch (informative_failure &) + { + return false; + } return internal_string_is_bookkeeping_path(utf8(normalized)); } bool bookkeeping_path::internal_string_is_bookkeeping_path(utf8 const & path) @@ -1928,6 +1935,11 @@ UNIT_TEST(paths, test_external_string_is char const * const no[] = {"../foo", "../foo/bar", "../foo/_MTN", +#ifdef WIN32 + "c:/foo/foo", // don't throw informative_failure exception +#else + "/foo/foo", // don't throw informative_failure exception +#endif 0 }; for (char const * const * c = yes; *c; ++c) UNIT_TEST_CHECK(bookkeeping_path ============================================================ --- roster.cc 04ac3afba58ec4c48d56f080d46ed924e51df79c +++ roster.cc 284a1b73aca418f411ae679fc317ae17cdefa80e @@ -823,11 +823,11 @@ roster_t::drop_detached_node(node_id nid I(downcast_to_dir_t(n)->children.empty()); // all right, kill it safe_erase(nodes, nid); - // can use safe_erase here, because while not every detached node appears in - // old_locations, all those that used to be in the tree do. and you should - // only ever be dropping nodes that were detached, not nodes that you just - // created and that have never been attached. - safe_erase(old_locations, nid); + + // Resolving a duplicate name conflict via drop one side requires dropping + // nodes that were never attached. So we erase the key without checking + // whether it was present. + old_locations.erase(nid); } ============================================================ --- roster.hh c83fc2636e82ac77a1446c42df169b44bb47197f +++ roster.hh 61c885cf725e7b20bbbe5a8e595904a4edc38c14 @@ -259,22 +259,6 @@ private: // and causes problems, in which case we should probably switch to the // former. // - // FIXME: This _is_ all a little nasty, because this can be a source of - // abstraction leak -- for instance, roster_merge's contract is that nodes - // involved in name-related conflicts will be detached in the roster it returns. - // Those nodes really should be allowed to be attached anywhere, or dropped, - // which is not actually expressible right now. Worse, whether or not they - // are in old_locations map is an implementation detail of roster_merge -- - // it may temporarily attach and then detach the nodes it creates, but this - // is not deterministic or part of its interface. The main time this would - // be a _problem_ is if we add interactive resolution of tree rearrangement - // conflicts -- if someone resolves a rename conflict by saying that one - // side wins, or by deleting one of the conflicting nodes, and this all - // happens in memory, then it may trigger a spurious invariant failure here. - // If anyone ever decides to add this kind of functionality, then it would - // definitely make sense to move this checking into editable_tree. For now, - // though, no such functionality is planned, so we'll see what happens. - // // The implementation itself uses the map old_locations. A node can be in // the following states: // -- attached, no entry in old_locations map ============================================================ --- roster_merge.cc 5c4a8791df3ca572589f2c7e91a565ed8f99dd32 +++ roster_merge.cc f0ff11778d5de232938b6d2244ab10a0cc5c10d9 @@ -264,8 +264,8 @@ namespace symbol const node_type("node_type"); symbol const orphaned_directory("orphaned_directory"); symbol const orphaned_file("orphaned_file"); + symbol const resolved_drop_left("resolved_drop_left"); symbol const resolved_drop_right("resolved_drop_right"); - symbol const resolved_drop_left("resolved_drop_left"); symbol const resolved_internal("resolved_internal"); symbol const resolved_rename_left("resolved_rename_left"); symbol const resolved_rename_right("resolved_rename_right"); @@ -502,7 +502,7 @@ put_duplicate_name_resolution(basic_io:: { case resolve_conflicts::none: break; - + case resolve_conflicts::content_user: switch (side) { @@ -515,7 +515,7 @@ put_duplicate_name_resolution(basic_io:: break; } break; - + case resolve_conflicts::rename: switch (side) { @@ -528,7 +528,7 @@ put_duplicate_name_resolution(basic_io:: break; } break; - + case resolve_conflicts::drop: switch (side) { @@ -541,7 +541,7 @@ put_duplicate_name_resolution(basic_io:: break; } break; - + default: I(false); } @@ -1573,10 +1573,10 @@ read_duplicate_name_conflict(basic_io::p // we don't care what type of file/dir these are; the right type will get // written back out. - pars.esym(syms::left_type); pars.str(); + pars.esym(syms::left_type); pars.str(); pars.esym (syms::left_name); pars.str(left_name); pars.esym(syms::left_file_id); pars.hex(); - + pars.esym(syms::right_type); pars.str(); pars.esym (syms::right_name); pars.str(right_name); pars.esym(syms::right_file_id); pars.hex(); @@ -1646,7 +1646,7 @@ read_duplicate_name_conflicts(basic_io:: read_duplicate_name_conflict(pars, conflict, left_roster, right_roster); conflicts.push_back(conflict); - + if (pars.tok.in.lookahead != EOF) pars.esym (syms::conflict); } @@ -1676,7 +1676,7 @@ validate_duplicate_name_conflicts(basic_ merge_conflict.left_resolution = file_conflict.left_resolution; merge_conflict.right_resolution = file_conflict.right_resolution; - + if (pars.tok.in.lookahead != EOF) pars.esym (syms::conflict); else @@ -1843,7 +1843,7 @@ roster_merge_result::read_conflict_file( basic_io::tokenizer tok(src); basic_io::parser pars(tok); std::string temp; - + // Read left, right, ancestor. pars.esym(syms::left); pars.hex(temp); @@ -1857,9 +1857,9 @@ roster_merge_result::read_conflict_file( db.get_roster(left_rid, left_roster, left_marking); db.get_roster(right_rid, right_roster, right_marking); - + read_conflict_file_core(pars, left_roster, right_roster, *this, false); - + } // roster_merge_result::read_conflict_file void @@ -1897,7 +1897,7 @@ roster_merge_result::write_conflict_file data dat(output.str()); write_data(file_name, dat, system_path("_MTN")); - + } // roster_merge_result::write_conflict_file static void @@ -1995,7 +1995,7 @@ parse_resolve_conflicts_opts (options co pars.sym(); pars.hex(); } - + read_conflict_file_core (pars, left_roster, right_roster, result, true); } else @@ -2027,6 +2027,65 @@ attach_node (lua_hooks & lua, } // attach_node +static void +resolve_duplicate_name_one_side(lua_hooks & lua, + resolve_conflicts::file_resolution_t const & resolution, + resolve_conflicts::file_resolution_t const & other_resolution, + file_path const & name, + file_id const & fid, + node_id const nid, + content_merge_adaptor & adaptor, + roster_t & result_roster) +{ + switch (resolution.first) + { + case resolve_conflicts::content_user: + { + N(other_resolution.first == resolve_conflicts::drop || + other_resolution.first == resolve_conflicts::rename, + F("inconsistent left/right resolutions for %s") % name); + + P(F("replacing content of %s with %s") % name % resolution.second->as_external()); + + file_id result_fid; + file_data parent_data, result_data; + data result_raw_data; + adaptor.get_version(fid, parent_data); + + read_data(*resolution.second, result_raw_data); + + result_data = file_data(result_raw_data); + calculate_ident(result_data, result_fid); + + file_t result_node = downcast_to_file_t(result_roster.get_node(nid)); + result_node->content = result_fid; + + adaptor.record_file(fid, result_fid, parent_data, result_data); + + attach_node(lua, result_roster, nid, name); + } + break; + + case resolve_conflicts::drop: + P(F("dropping %s") % name); + result_roster.drop_detached_node(nid); + break; + + case resolve_conflicts::rename: + P(F("renaming %s to %s") % name % *resolution.second); + attach_node + (lua, result_roster, nid, file_path_internal (resolution.second->as_internal())); + break; + + case resolve_conflicts::none: + N(false, F("no resolution provided for duplicate_name %s") % name); + break; + + default: + N(false, F("%s: invalid resolution for duplicate_name %s") % image (resolution.first) % name); + } +} // resolve_duplicate_name_one_side + void roster_merge_result::resolve_duplicate_name_conflicts(lua_hooks & lua, roster_t const & left_roster, @@ -2052,54 +2111,16 @@ roster_merge_result::resolve_duplicate_n node_id right_nid= conflict.right_nid; file_path left_name, right_name; + file_id left_fid, right_fid; - left_roster.get_name(left_nid, left_name); - right_roster.get_name(right_nid, right_name); + left_roster.get_file_details(left_nid, left_fid, left_name); + right_roster.get_file_details(right_nid, right_fid, right_name); - switch (conflict.left_resolution.first) - { - case resolve_conflicts::content_user: - P(F("replacing content of %s with %s") % left_name % conflict.left_resolution.second->as_external()); - I(false); // FIXME_RESOLVE_CONFLICTS: not implemented - // something like adaptor.record_file(); - break; + resolve_duplicate_name_one_side + (lua, conflict.left_resolution, conflict.right_resolution, left_name, left_fid, left_nid, adaptor, roster); - case resolve_conflicts::drop: - P(F("dropping %s") % left_name); - I(false); // FIXME_RESOLVE_CONFLICTS: not implemented - // something like drop_detached_node(); - break; - - case resolve_conflicts::rename: - P(F("renaming %s to %s") % left_name % conflict.left_resolution.second); - attach_node - (lua, this->roster, left_nid, file_path_internal (conflict.left_resolution.second->as_internal())); - break; - - case resolve_conflicts::none: - N(false, F("no resolution provided for duplicate_name %s") % left_name); - break; - - default: - N(false, F("%s: invalid resolution for this conflict") % image (conflict.left_resolution.first)); - } - - switch (conflict.right_resolution.first) - { - case resolve_conflicts::rename: - P(F("renaming %s to %s") % right_name % conflict.right_resolution.second); - attach_node - (lua, this->roster, right_nid, file_path_internal (conflict.right_resolution.second->as_internal())); - break; - - case resolve_conflicts::none: - // Just keep current name - this->roster.attach_node (right_nid, right_name); - break; - - default: - N(false, F("%s: invalid resolution for this conflict") % image (conflict.right_resolution.first)); - } + resolve_duplicate_name_one_side + (lua, conflict.right_resolution, conflict.left_resolution, right_name, right_fid, right_nid, adaptor, roster); } // end for duplicate_name_conflicts.clear(); ============================================================ --- tests/resolve_conflict_all_resolutions/__driver__.lua 2c013201e50e953df1ca9b55ad726b4c633c9610 +++ tests/resolve_conflict_all_resolutions/__driver__.lua eae4fa4b06ac70013d1cabc187778aa0a8f6685b @@ -4,25 +4,29 @@ mtn_setup() mtn_setup() -- Generate a conflicts file, with one conflict for each type of --- resolution list of resolutions is in roster_merge.cc, syms --- declaration; any sym that starts with "resolved". --- +-- resolution. The list of currently supported resolutions is in +-- roster_merge.cc, syms declaration; any sym that starts with +-- "resolved". +-- -- resolution file -- resolved_drop_left checkout_left.sh abe -- resolved_drop_right checkout_right.sh beth +-- resolved_internal simple_file +-- resolved_rename_left thermostat.c -> thermostat-westinghouse.c -- resolved_rename_right thermostat.c -> thermostat-honeywell.c --- resolved_rename_left thermostat.c -> thermostat-westinghouse.c --- resolved_user randomfile +-- resolved_user user_file -- resolved_user_left checkout_left.sh beth -- resolved_user_right checkout_right.sh abe -- --- We can't set 'resolved_internal'. +-- We can't set 'resolved_internal' directly; it is set by 'conflicts store'. -addfile("randomfile", "blah blah blah") +addfile("simple_file", "simple\none\ntwo\nthree\n") +addfile("user_file", "blah blah blah") commit() base = base_revision() -writefile("randomfile", "randomfile abe 1") +addfile("simple_file", "simple\nzero\none\ntwo\nthree\n") +writefile("user_file", "user_file abe 1") addfile("checkout_left.sh", "checkout_left.sh abe 1") addfile("checkout_right.sh", "checkout_right.sh abe 1") addfile("thermostat.c", "thermostat westinghouse") @@ -31,7 +35,8 @@ revert_to(base) revert_to(base) -writefile("randomfile", "randomfile beth 1") +addfile("simple_file", "simple\none\ntwo\nthree\nfour\n") +writefile("user_file", "user_file beth 1") addfile("checkout_left.sh", "checkout_left.sh beth 1") addfile("checkout_right.sh", "checkout_right.sh beth 1") addfile("thermostat.c", "thermostat honeywell") @@ -40,43 +45,51 @@ mkdir("resolutions") -- Don't use _MTN/conflicts, to test that capability mkdir("resolutions") -check (mtn("conflicts", "--conflict_file=resolutions/conflicts", "store", abe_1, beth_1), 0, true, nil) +check (mtn("conflicts", "--conflicts-file=resolutions/conflicts", "store", abe_1, beth_1), 0, nil, nil) check(samefilestd("conflicts-1", "resolutions/conflicts")) -rename("stdout", "resolutions/conflicts") +check(mtn("conflicts", "--conflicts-file=resolutions/conflicts", "show_first"), 0, nil, true) +canonicalize("stderr") +check(samefilestd("show_first-checkout_left", "stderr")) -check(mtn("conflicts", "--conflict_file=resolutions/conflicts", "show_first", resolution), 0, true, nil) -canonicalize("stdout") -check(samefilestd("show_first-checkout_left"), "stdout") +writefile("resolutions/checkout_left.sh", "checkout_left.sh beth 2") +check(mtn("conflicts", "--conflicts-file=resolutions/conflicts", "resolve_first_left", "drop"), 0, nil, nil) +check(mtn("conflicts", "--conflicts-file=resolutions/conflicts", "resolve_first_right", "user", "resolutions/checkout_left.sh"), 0, nil, nil) -resolution = "resolved_drop_left\n resolved_user_right \"resolutions/checkout_left.sh\"" -check(mtn("conflicts", "--conflict_file=resolutions/conflicts", "resolve_first", resolution), 0, nil, nil) +check(mtn("conflicts", "--conflicts-file=resolutions/conflicts", "show_first"), 0, nil, true) +canonicalize("stderr") +check(samefilestd("show_first-checkout_right", "stderr")) -check(mtn("conflicts", "--conflict_file=resolutions/conflicts", "show_first", resolution), 0, true, nil) -canonicalize("stdout") -check(samefilestd("show_first-checkout_right"), "stdout") +writefile("resolutions/checkout_right.sh", "checkout_right.sh beth 2") +check(mtn("conflicts", "--conflicts-file=resolutions/conflicts", "resolve_first_right", "drop"), 0, nil, nil) +check(mtn("conflicts", "--conflicts-file=resolutions/conflicts", "resolve_first_left", "user", "resolutions/checkout_right.sh"), 0, nil, nil) -resolution = "resolved_drop_rightt\n resolved_user_left \"resolutions/checkout_right.sh\"" -check(mtn("conflicts", "--conflict_file=resolutions/conflicts", "resolve_first", resolution), 0, nil, nil) +check(mtn("conflicts", "--conflicts-file=resolutions/conflicts", "show_first"), 0, nil, true) +canonicalize("stderr") +check(samefilestd("show_first-thermostat", "stderr")) -check(mtn("conflicts", "--conflict_file=resolutions/conflicts", "show_first", resolution), 0, true, nil) -canonicalize("stdout") -check(samefilestd("show_first-thermostat"), "stdout") +check(mtn("conflicts", "--conflicts-file=resolutions/conflicts", "resolve_first_left", "rename", "thermostat-westinghouse.c"), 0, nil, nil) +check(mtn("conflicts", "--conflicts-file=resolutions/conflicts", "resolve_first_right", "rename", "thermostat-honeywell.c"), 0, nil, nil) -resolution = "resolved_rename_left \"thermostat-westinghouse.c\"\n resolved_rename_right \"thermostat-honeywell.c\"" -check(mtn("conflicts", "--conflict_file=resolutions/conflicts", "resolve_first", resolution), 0, nil, nil) +check(mtn("conflicts", "--conflicts-file=resolutions/conflicts", "show_first"), 0, nil, true) +canonicalize("stderr") +check(samefilestd("show_first-user", "stderr")) +writefile("resolutions/user_file", "user_file merged") +check(mtn("conflicts", "--conflicts-file=resolutions/conflicts", "resolve_first", "user", "resolutions/user_file"), 0, nil, nil) + check(samefilestd("conflicts-resolved", "resolutions/conflicts")) -- This succeeds -check(mtn("merge", "--resolve-conflicts-file", "resolutions/conflicts"), 0, true, nil) -canonicalize("stdout") -check(samefilestd("merge-1", "stdout")) +check(mtn("merge", "--resolve-conflicts-file", "resolutions/conflicts"), 0, nil, true) +canonicalize("stderr") +check(samefilestd("merge-1", "stderr")) -- Verify user specified resolution files check(mtn("update"), 0, nil, false) check("checkout_left.sh beth 2" == readfile("checkout_left.sh")) check("checkout_right.sh beth 2" == readfile("checkout_right.sh")) +check("user_file merged" == readfile("user_file")) -- end of file ============================================================ --- tests/resolve_conflict_all_resolutions/conflicts-1 699148173160c1d22938da045b8bbe737f7b518f +++ tests/resolve_conflict_all_resolutions/conflicts-1 bf24db060f7d4f25c09eff14083d02b9e6a2533b @@ -1,19 +1,46 @@ - left [1337cb1059c4bc3e376b14381b43e9383c654da1] - right [d5f1dd136c86b5bbd5e71b0c3365667e328af492] -ancestor [b3ac8a77cee78263b66800c635441ecb1f259a42] + left [deb9772dea922e56623683f9fa58558670cda9a6] + right [670f6a2810983d1f6ce4c6c076f5f685ffd4b17c] +ancestor [59f6060f7ed4dd514c2e0a9bd291ed9a9af29e23] conflict duplicate_name left_type "added file" - left_name "checkout.sh" - left_file_id [61b8d4fb0e5d78be111f691b955d523c782fa92e] + left_name "checkout_left.sh" + left_file_id [ae5fe55181c0307c705d0b05fdc1147fc4afd05c] right_type "added file" - right_name "checkout.sh" -right_file_id [dd6805ae36432d6edcbdff6ea578ea981ffa2144] + right_name "checkout_left.sh" +right_file_id [355315653eb77ade4804e42a2ef30c89387e1a2d] conflict duplicate_name left_type "added file" + left_name "checkout_right.sh" + left_file_id [a2c187e7107dce500e25475fb26ae1e19a18e3e4] + right_type "added file" + right_name "checkout_right.sh" +right_file_id [838cefbcc3a1add031c1d3b407c4ba614bb663bc] + + conflict duplicate_name + left_type "added file" left_name "thermostat.c" left_file_id [4cdcec6fa2f9d5c075d5b80d03c708c8e4801196] right_type "added file" right_name "thermostat.c" right_file_id [7e9f2712c5d3570815f1546772d9119474d32afc] + + conflict content + node_type "file" + ancestor_name "simple_file" + ancestor_file_id [10c3f68703db3019422c7cb952de8009b66d6ba3] + left_name "simple_file" + left_file_id [5714687dfbef1a2103827321da616343e6ffcbe3] + right_name "simple_file" + right_file_id [b70b9605783c1c4195f490dd0feafc743731478c] +resolved_internal + + conflict content + node_type "file" + ancestor_name "user_file" +ancestor_file_id [25ed8e31b995bb927966616df2a42b979a2717f0] + left_name "user_file" + left_file_id [5ce20f4b9a1da51992d03488cd0be4874324db75] + right_name "user_file" + right_file_id [faa330de9182327a12ef9c7e5eaa0bea7f74ecad] ============================================================ --- tests/resolve_duplicate_name_conflict/__driver__.lua 320c07fc8141d2eac97f2f6a71e6b9d4754799c9 +++ tests/resolve_duplicate_name_conflict/__driver__.lua de6ac22eec67432fccd8a0a2cddac622b639e7c3 @@ -36,7 +36,7 @@ check(mtn("merge"), 1, nil, false) -- For checkout.sh, she retrieves Abe's version to merge with hers, -- using 'automate get_file'. This requires knowing the file id -- of Abe's commit, which we get from 'automate show_conflicts'. --- +-- -- mtn is not up to actually doing the merge of checkout.sh yet (that -- requires sutures), so we specify a conflict resolution that drops -- Abe's version and keeps Beth's manual merge. @@ -76,10 +76,12 @@ check(samefilestd("conflicts-resolved", check(samefilestd("conflicts-resolved", "_MTN/conflicts")) -- This succeeds -check(mtn("merge", "--resolve-conflicts-file", "_MTN/conflicts"), 0, true, nil) -canonicalize("stdout") -check(samefilestd("merge-1", "stdout")) +check(mtn("merge", "--resolve-conflicts-file", "_MTN/conflicts"), 0, nil, true) +canonicalize("stderr") +check(samefilestd("merge-1", "stderr")) +check(mtn("conflicts", "clean"), 0, nil, true) + check(mtn("update"), 0, nil, false) check("checkout.sh beth 2" == readfile("checkout.sh")) ============================================================ --- work.cc 0ef66673796d2a408587d70530944a5a8747d488 +++ work.cc 815785ca53cb178b928a3e61ba65f03bdeb49fd3 @@ -825,18 +825,18 @@ addition_builder::visit_dir(file_path co { if (!recursive) { bool warn = false; - + // If the db can ever be stored in a dir // then revisit this logic I(!db.is_dbfile(path)); - + if (!respect_ignore) warn = !directory_empty(path); else if (respect_ignore && !work.ignore_file(path)) { vector children; read_directory(path, children, children); - + for (vector::const_iterator i = children.begin(); i != children.end(); ++i) { @@ -857,12 +857,12 @@ addition_builder::visit_dir(file_path co } } } - + if (warn) W(F("Non-recursive add: Files in the directory '%s' " "will not be added automatically.") % path); } - + this->visit_file(path); return true; } @@ -967,6 +967,11 @@ struct content_merge_empty_adaptor : pub file_data const &, file_data const &, file_data const &) { I(false); } + virtual void record_file(file_id const &, + file_id const &, + file_data const &, + file_data const &) + { I(false); } virtual void get_ancestral_roster(node_id, revision_id &, boost::shared_ptr &) { I(false); } };