monotone

monotone Mtn Source Tree

Root/cmd_merging.cc

1// Copyright (C) 2002 Graydon Hoare <graydon@pobox.com>
2//
3// This program is made available under the GNU GPL version 2.0 or
4// greater. See the accompanying file COPYING for details.
5//
6// This program is distributed WITHOUT ANY WARRANTY; without even the
7// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
8// PURPOSE.
9
10#include "base.hh"
11#include <cstring>
12#include <iostream>
13#include <iomanip>
14
15#include "cmd.hh"
16#include "diff_patch.hh"
17#include "merge.hh"
18#include "restrictions.hh"
19#include "revision.hh"
20#include "roster_merge.hh"
21#include "transforms.hh"
22#include "update.hh"
23#include "work.hh"
24#include "safe_map.hh"
25#include "ui.hh"
26#include "app_state.hh"
27#include "simplestring_xform.hh"
28
29using std::cout;
30using std::make_pair;
31using std::map;
32using std::set;
33using std::string;
34using std::vector;
35using std::strlen;
36
37using boost::shared_ptr;
38
39static void
40three_way_merge(roster_t const & ancestor_roster,
41 roster_t const & left_roster, roster_t const & right_roster,
42 roster_merge_result & result)
43{
44 MM(ancestor_roster);
45 MM(left_roster);
46 MM(right_roster);
47
48 // Make some fake rids
49 revision_id ancestor_rid(fake_id()); MM(ancestor_rid);
50 revision_id left_rid(fake_id()); MM(left_rid);
51 revision_id right_rid(fake_id()); MM(right_rid);
52
53 // Mark up the ANCESTOR
54 marking_map ancestor_markings; MM(ancestor_markings);
55 mark_roster_with_no_parents(ancestor_rid, ancestor_roster, ancestor_markings);
56
57 // Mark up the LEFT roster
58 marking_map left_markings; MM(left_markings);
59 mark_roster_with_one_parent(ancestor_roster, ancestor_markings,
60 left_rid, left_roster, left_markings);
61
62 // Mark up the RIGHT roster
63 marking_map right_markings; MM(right_markings);
64 mark_roster_with_one_parent(ancestor_roster, ancestor_markings,
65 right_rid, right_roster, right_markings);
66
67 // Make the synthetic graph, by creating uncommon ancestor sets
68 std::set<revision_id> left_uncommon_ancestors, right_uncommon_ancestors;
69 safe_insert(left_uncommon_ancestors, left_rid);
70 safe_insert(right_uncommon_ancestors, right_rid);
71
72 // And do the merge
73 roster_merge(left_roster, left_markings, left_uncommon_ancestors,
74 right_roster, right_markings, right_uncommon_ancestors,
75 result);
76}
77
78static bool
79pick_branch_for_update(revision_id chosen_rid, app_state & app)
80{
81 bool switched_branch = false;
82
83 // figure out which branches the target is in
84 vector< revision<cert> > certs;
85 app.db.get_revision_certs(chosen_rid, branch_cert_name, certs);
86 erase_bogus_certs(certs, app);
87
88 set< branch_name > branches;
89 for (vector< revision<cert> >::const_iterator i = certs.begin();
90 i != certs.end(); i++)
91 {
92 cert_value b;
93 decode_base64(i->inner().value, b);
94 branches.insert(branch_name(b()));
95 }
96
97 if (branches.find(app.opts.branchname) != branches.end())
98 {
99 L(FL("using existing branch %s") % app.opts.branchname());
100 }
101 else
102 {
103 P(F("target revision is not in current branch"));
104 if (branches.size() > 1)
105 {
106 // multiple non-matching branchnames
107 string branch_list;
108 for (set<branch_name>::const_iterator i = branches.begin();
109 i != branches.end(); i++)
110 branch_list += "\n " + (*i)();
111 N(false, F("target revision is in multiple branches:%s\n\n"
112 "try again with explicit --branch") % branch_list);
113 }
114 else if (branches.size() == 1)
115 {
116 // one non-matching, inform and update
117 app.opts.branchname = *(branches.begin());
118 switched_branch = true;
119 }
120 else
121 {
122 I(branches.size() == 0);
123 W(F("target revision not in any branch\n"
124 "next commit will use branch %s")
125 % app.opts.branchname);
126 }
127 }
128 return switched_branch;
129}
130
131CMD(update, "update", "", CMD_REF(workspace), "",
132 N_("Updates the workspace"),
133 N_("This command modifies your workspace to be based off of a "
134 "different revision, preserving uncommitted changes as it does so. "
135 "If a revision is given, update the workspace to that revision. "
136 "If not, update the workspace to the head of the branch."),
137 options::opts::branch | options::opts::revision)
138{
139 if (args.size() > 0)
140 throw usage(execid);
141
142 if (app.opts.revision_selectors.size() > 1)
143 throw usage(execid);
144
145 app.require_workspace();
146
147 // Figure out where we are
148 parent_map parents;
149 app.work.get_parent_rosters(parents);
150
151 N(parents.size() == 1,
152 F("this command can only be used in a single-parent workspace"));
153
154 revision_id old_rid = parent_id(parents.begin());
155 N(!null_id(old_rid),
156 F("this workspace is a new project; cannot update"));
157
158 // Figure out where we're going
159
160 revision_id chosen_rid;
161 if (app.opts.revision_selectors.size() == 0)
162 {
163 P(F("updating along branch '%s'") % app.opts.branchname);
164 set<revision_id> candidates;
165 pick_update_candidates(old_rid, app, candidates);
166 N(!candidates.empty(),
167 F("your request matches no descendents of the current revision\n"
168 "in fact, it doesn't even match the current revision\n"
169 "maybe you want something like --revision=h:%s")
170 % app.opts.branchname);
171 if (candidates.size() != 1)
172 {
173 P(F("multiple update candidates:"));
174 for (set<revision_id>::const_iterator i = candidates.begin();
175 i != candidates.end(); ++i)
176 P(i18n_format(" %s") % describe_revision(app, *i));
177 P(F("choose one with '%s update -r<id>'") % ui.prog_name);
178 E(false, F("multiple update candidates remain after selection"));
179 }
180 chosen_rid = *(candidates.begin());
181 }
182 else
183 {
184 complete(app, app.opts.revision_selectors[0](), chosen_rid);
185 N(app.db.revision_exists(chosen_rid),
186 F("no such revision '%s'") % chosen_rid);
187 }
188 I(!null_id(chosen_rid));
189
190 // do this notification before checking to see if we can bail out early,
191 // because when you are at one of several heads, and you hit update, you
192 // want to know that merging would let you update further.
193 notify_if_multiple_heads(app);
194
195 if (old_rid == chosen_rid)
196 {
197 P(F("already up to date at %s") % old_rid);
198 // do still switch the workspace branch, in case they have used
199 // update to switch branches.
200 if (!app.opts.branchname().empty())
201 app.make_branch_sticky();
202 return;
203 }
204
205 P(F("selected update target %s") % chosen_rid);
206
207 // Fiddle around with branches, in an attempt to guess what the user
208 // wants.
209 bool switched_branch = pick_branch_for_update(chosen_rid, app);
210 if (switched_branch)
211 P(F("switching to branch %s") % app.opts.branchname());
212
213 // Okay, we have a target, we have a branch, let's do this merge!
214
215 // We have:
216 //
217 // old --> working
218 // | |
219 // V V
220 // chosen --> merged
221 //
222 // - old is the revision specified in _MTN/revision
223 // - working is based on old and includes the workspace's changes
224 // - chosen is the revision we're updating to and will end up in _MTN/revision
225 // - merged is the merge of working and chosen, that will become the new
226 // workspace
227 //
228 // we apply the working to merged cset to the workspace
229 // and write the cset from chosen to merged changeset in _MTN/work
230
231 temp_node_id_source nis;
232
233 // Get the OLD and WORKING rosters
234 database::roster_t_cp old_roster
235 = parent_cached_roster(parents.begin()).first;
236 MM(*old_roster);
237 roster_t working_roster; MM(working_roster);
238 app.work.get_current_roster_shape(working_roster, nis);
239 app.work.update_current_roster_from_filesystem(working_roster);
240
241 // Get the CHOSEN roster
242 roster_t chosen_roster; MM(chosen_roster);
243 app.db.get_roster(chosen_rid, chosen_roster);
244
245 // And finally do the merge
246 roster_merge_result result;
247 three_way_merge(*old_roster, working_roster, chosen_roster, result);
248
249 roster_t & merged_roster = result.roster;
250
251 map<file_id, file_path> paths;
252 get_content_paths(working_roster, paths);
253
254 content_merge_workspace_adaptor wca(app, old_roster, paths);
255 resolve_merge_conflicts(working_roster, chosen_roster,
256 result, wca, app);
257
258 // Make sure it worked...
259 I(result.is_clean());
260 merged_roster.check_sane(true);
261
262 // Now finally modify the workspace
263 cset update;
264 make_cset(working_roster, merged_roster, update);
265 app.work.perform_content_update(update, wca);
266
267 revision_t remaining;
268 make_revision_for_workspace(chosen_rid, chosen_roster,
269 merged_roster, remaining);
270
271 // small race condition here...
272 app.work.put_work_rev(remaining);
273 app.work.update_any_attrs();
274 app.work.maybe_update_inodeprints();
275
276 if (!app.opts.branchname().empty())
277 app.make_branch_sticky();
278 if (switched_branch)
279 P(F("switched branch; next commit will use branch %s") % app.opts.branchname());
280 P(F("updated to base revision %s") % chosen_rid);
281}
282
283// Subroutine of CMD(merge) and CMD(explicit_merge). Merge LEFT with RIGHT,
284// placing results onto BRANCH. Note that interactive_merge_and_store may
285// bomb out, and therefore so may this.
286static void
287merge_two(revision_id const & left, revision_id const & right,
288 branch_name const & branch, string const & caller, app_state & app,
289 std::ostream & output, bool automate)
290{
291 // The following mess constructs a neatly formatted log message that looks
292 // like this:
293 // CALLER of 'LEFT'
294 // and 'RIGHT'
295 // to branch 'BRANCH'
296 // where the last line is left out if we're merging onto the current branch.
297 // We use a stringstream because boost::format does not support %-*s.
298 using std::ostringstream;
299 using std::setw;
300 using std::max;
301
302 ostringstream log;
303 size_t fieldwidth = max(caller.size() + strlen(" of '"), strlen("and '"));
304
305 if (branch != app.opts.branchname)
306 fieldwidth = max(fieldwidth, strlen("to branch '"));
307
308 log << setw(fieldwidth - strlen(" of '")) << caller << " of '" << left
309 << "'\n" << setw(fieldwidth) << "and '" << right
310 << "'\n";
311
312 if (branch != app.opts.branchname)
313 log << setw(fieldwidth) << "to branch '" << branch << "'\n";
314
315 // Now it's time for the real work.
316 if (automate)
317 {
318 output << left << " " << right << " ";
319 }
320 else
321 {
322 P(F("[left] %s") % left);
323 P(F("[right] %s") % right);
324 }
325
326 revision_id merged;
327 transaction_guard guard(app.db);
328 interactive_merge_and_store(left, right, merged, app);
329
330 app.get_project().put_standard_certs_from_options(merged,
331 branch,
332 utf8(log.str()));
333
334 guard.commit();
335 if (automate)
336 output << merged << "\n";
337 else
338 P(F("[merged] %s") % merged);
339}
340
341// should merge support --message, --message-file? It seems somewhat weird,
342// since a single 'merge' command may perform arbitrarily many actual merges.
343// (Possibility: append the --message/--message-file text to the synthetic
344// log message constructed in merge_two().)
345CMD(merge, "merge", "", CMD_REF(tree), "",
346 N_("Merges unmerged heads of a branch"),
347 "",
348 options::opts::branch | options::opts::date | options::opts::author)
349{
350 typedef std::pair<revision_id, revision_id> revpair;
351 typedef set<revision_id>::const_iterator rid_set_iter;
352
353 if (args.size() != 0)
354 throw usage(execid);
355
356 N(app.opts.branchname() != "",
357 F("please specify a branch, with --branch=BRANCH"));
358
359 set<revision_id> heads;
360 app.get_project().get_branch_heads(app.opts.branchname, heads);
361
362 N(heads.size() != 0, F("branch '%s' is empty") % app.opts.branchname);
363 if (heads.size() == 1)
364 {
365 P(F("branch '%s' is already merged") % app.opts.branchname);
366 return;
367 }
368
369 P(FP("%d head on branch '%s'", "%d heads on branch '%s'", heads.size())
370 % heads.size() % app.opts.branchname);
371
372 map<revision_id, revpair> heads_for_ancestor;
373 set<revision_id> ancestors;
374 size_t pass = 1, todo = heads.size() - 1;
375
376 // If there are more than two heads to be merged, on each iteration we
377 // merge a pair whose least common ancestor is not an ancestor of any
378 // other pair's least common ancestor. For example, if the history graph
379 // looks like this:
380 //
381 // X
382 // / \. (periods to prevent multi-line
383 // Y C comment warnings)
384 // / \.
385 // A B
386 //
387 // A and B will be merged first, and then the result will be merged with C.
388 while (heads.size() > 2)
389 {
390 P(F("merge %d / %d:") % pass % todo);
391 P(F("calculating best pair of heads to merge next"));
392
393 // For every pair of heads, determine their merge ancestor, and
394 // remember the ancestor->head mapping.
395 for (rid_set_iter i = heads.begin(); i != heads.end(); ++i)
396 for (rid_set_iter j = i; j != heads.end(); ++j)
397 {
398 // It is not possible to initialize j to i+1 (set iterators
399 // expose neither operator+ nor a nondestructive next() method)
400 if (j == i)
401 continue;
402
403 revision_id ancestor;
404 find_common_ancestor_for_merge(*i, *j, ancestor, app);
405
406 // More than one pair might have the same ancestor (e.g. if we
407 // have three heads all with the same parent); as this table
408 // will be recalculated on every pass, we just take the first
409 // one we find.
410 if (ancestors.insert(ancestor).second)
411 safe_insert(heads_for_ancestor, std::make_pair(ancestor, revpair(*i, *j)));
412 }
413
414 // Erasing ancestors from ANCESTORS will now produce a set of merge
415 // ancestors each of which is not itself an ancestor of any other
416 // merge ancestor.
417 erase_ancestors(ancestors, app);
418 I(ancestors.size() > 0);
419
420 // Take the first ancestor from the above set and merge its
421 // corresponding pair of heads.
422 revpair p = heads_for_ancestor[*ancestors.begin()];
423
424 merge_two(p.first, p.second, app.opts.branchname, string("merge"), app, std::cout, false);
425
426 ancestors.clear();
427 heads_for_ancestor.clear();
428 app.get_project().get_branch_heads(app.opts.branchname, heads);
429 pass++;
430 }
431
432 // Last one.
433 I(pass == todo);
434 if (todo > 1)
435 P(F("merge %d / %d:") % pass % todo);
436
437 rid_set_iter i = heads.begin();
438 revision_id left = *i++;
439 revision_id right = *i++;
440 I(i == heads.end());
441
442 merge_two(left, right, app.opts.branchname, string("merge"), app, std::cout, false);
443 P(F("note: your workspaces have not been updated"));
444}
445
446CMD(propagate, "propagate", "", CMD_REF(tree),
447 N_("SOURCE-BRANCH DEST-BRANCH"),
448 N_("Merges from one branch to another asymmetrically"),
449 "",
450 options::opts::date | options::opts::author | options::opts::message | options::opts::msgfile)
451{
452 if (args.size() != 2)
453 throw usage(execid);
454 args_vector a = args;
455 a.push_back(arg_type());
456 process(app, make_command_id("tree merge_into_dir"), a);
457}
458
459CMD(merge_into_dir, "merge_into_dir", "", CMD_REF(tree),
460 N_("SOURCE-BRANCH DEST-BRANCH DIR"),
461 N_("Merges one branch into a subdirectory in another branch"),
462 "",
463 options::opts::date | options::opts::author | options::opts::message | options::opts::msgfile)
464{
465 // This is a special merge operator, but very useful for people
466 // maintaining "slightly disparate but related" trees. It does a one-way
467 // merge; less powerful than putting things in the same branch and also
468 // more flexible.
469 //
470 // 1. Check to see if src and dst branches are merged, if not abort, if so
471 // call heads N1 and N2 respectively.
472 //
473 // 2. (FIXME: not yet present) Run the hook propagate ("src-branch",
474 // "dst-branch", N1, N2) which gives the user a chance to massage N1 into
475 // a state which is likely to "merge nicely" with N2, eg. edit pathnames,
476 // omit optional files of no interest.
477 //
478 // 3. Do a normal 2 or 3-way merge on N1 and N2, depending on the
479 // existence of common ancestors.
480 //
481 // 4. Save the results as the delta (N2,M), the ancestry edges (N1,M)
482 // and (N2,M), and the cert (N2,dst).
483 //
484 // There are also special cases we have to check for where no merge is
485 // actually necessary, because there hasn't been any divergence since the
486 // last time propagate was run.
487 //
488 // If dir is not the empty string, rename the root of N1 to have the name
489 // 'dir' in the merged tree. (ie, it has name "basename(dir)", and its
490 // parent node is "N2.get_node(dirname(dir))")
491
492 set<revision_id> src_heads, dst_heads;
493
494 if (args.size() != 3)
495 throw usage(execid);
496
497 app.get_project().get_branch_heads(branch_name(idx(args, 0)()), src_heads);
498 app.get_project().get_branch_heads(branch_name(idx(args, 1)()), dst_heads);
499
500 N(src_heads.size() != 0, F("branch '%s' is empty") % idx(args, 0)());
501 N(src_heads.size() == 1, F("branch '%s' is not merged") % idx(args, 0)());
502
503 N(dst_heads.size() != 0, F("branch '%s' is empty") % idx(args, 1)());
504 N(dst_heads.size() == 1, F("branch '%s' is not merged") % idx(args, 1)());
505
506 set<revision_id>::const_iterator src_i = src_heads.begin();
507 set<revision_id>::const_iterator dst_i = dst_heads.begin();
508
509 P(F("propagating %s -> %s") % idx(args,0) % idx(args,1));
510 P(F("[source] %s") % *src_i);
511 P(F("[target] %s") % *dst_i);
512
513 // check for special cases
514 if (*src_i == *dst_i || is_ancestor(*src_i, *dst_i, app))
515 {
516 P(F("branch '%s' is up-to-date with respect to branch '%s'")
517 % idx(args, 1)() % idx(args, 0)());
518 P(F("no action taken"));
519 }
520 else if (is_ancestor(*dst_i, *src_i, app))
521 {
522 P(F("no merge necessary; putting %s in branch '%s'")
523 % (*src_i) % idx(args, 1)());
524 transaction_guard guard(app.db);
525 app.get_project().put_revision_in_branch(*src_i,
526 branch_name(idx(args, 1)()));
527 guard.commit();
528 }
529 else
530 {
531 revision_id merged;
532 transaction_guard guard(app.db);
533
534 {
535 revision_id const & left_rid(*src_i), & right_rid(*dst_i);
536 roster_t left_roster, right_roster;
537 MM(left_roster);
538 MM(right_roster);
539 marking_map left_marking_map, right_marking_map;
540 set<revision_id>
541 left_uncommon_ancestors,
542 right_uncommon_ancestors;
543
544 app.db.get_roster(left_rid, left_roster, left_marking_map);
545 app.db.get_roster(right_rid, right_roster, right_marking_map);
546 app.db.get_uncommon_ancestors(left_rid, right_rid,
547 left_uncommon_ancestors,
548 right_uncommon_ancestors);
549
550 if (!idx(args,2)().empty())
551 {
552 dir_t moved_root = left_roster.root();
553 file_path pth = file_path_external(idx(args, 2));
554 file_path dir;
555 path_component base;
556 MM(dir);
557 pth.dirname_basename(dir, base);
558
559 N(right_roster.has_node(dir),
560 F("Path %s not found in destination tree.") % pth);
561 node_t parent = right_roster.get_node(dir);
562 moved_root->parent = parent->self;
563 moved_root->name = base;
564 marking_map::iterator
565 i = left_marking_map.find(moved_root->self);
566 I(i != left_marking_map.end());
567 i->second.parent_name.clear();
568 i->second.parent_name.insert(left_rid);
569 }
570
571 roster_merge_result result;
572 roster_merge(left_roster,
573 left_marking_map,
574 left_uncommon_ancestors,
575 right_roster,
576 right_marking_map,
577 right_uncommon_ancestors,
578 result);
579
580 content_merge_database_adaptor
581 dba(app, left_rid, right_rid, left_marking_map);
582
583 resolve_merge_conflicts(left_roster, right_roster,
584 result, dba, app);
585
586 {
587 dir_t moved_root = left_roster.root();
588 moved_root->parent = the_null_node;
589 moved_root->name = path_component();
590 }
591
592 // Write new files into the db.
593 store_roster_merge_result(left_roster, right_roster, result,
594 left_rid, right_rid, merged,
595 app);
596 }
597
598 bool log_message_given;
599 utf8 log_message;
600 process_commit_message_args(log_message_given, log_message, app);
601 if (!log_message_given)
602 log_message = utf8((FL("propagate from branch '%s' (head %s)\n"
603 " to branch '%s' (head %s)\n")
604 % idx(args, 0) % (*src_i)
605 % idx(args, 1) % (*dst_i)).str());
606
607 app.get_project().put_standard_certs_from_options(merged,
608 branch_name(idx(args, 1)()),
609 log_message);
610
611 guard.commit();
612 P(F("[merged] %s") % merged);
613 }
614}
615
616CMD(merge_into_workspace, "merge_into_workspace", "", CMD_REF(tree),
617 N_("OTHER-REVISION"),
618 N_("Merges a revision into the current workspace's base revision"),
619 N_("Merges OTHER-REVISION into the current workspace's base revision, "
620 "and update the current workspace with the result. There can be no "
621 "pending changes in the current workspace. Both OTHER-REVISION and "
622 "the workspace's base revision will be recorded as parents on commit. "
623 "The workspace's selected branch is not changed."),
624 options::opts::none)
625{
626 revision_id left_id, right_id;
627 database::cached_roster left, right;
628 roster_t working_roster;
629
630 if (args.size() != 1)
631 throw usage(execid);
632
633 app.require_workspace();
634
635 // Get the current state of the workspace.
636
637 // This command cannot be applied to a workspace with more than one parent
638 // (revs can have no more than two parents).
639 {
640 parent_map parents;
641 app.work.get_parent_rosters(parents);
642 N(parents.size() == 1,
643 F("this command can only be used in a single-parent workspace"));
644
645 temp_node_id_source nis;
646 app.work.get_current_roster_shape(working_roster, nis);
647 app.work.update_current_roster_from_filesystem(working_roster);
648
649 N(parent_roster(parents.begin()) == working_roster,
650 F("'%s' can only be used in a workspace with no pending changes") %
651 join_words(execid)());
652
653 left_id = parent_id(parents.begin());
654 left = parent_cached_roster(parents.begin());
655 }
656
657 complete(app, idx(args, 0)(), right_id);
658 app.db.get_roster(right_id, right);
659 N(!(left_id == right_id), F("workspace is already at revision %s") % left_id);
660
661 set<revision_id> left_uncommon_ancestors, right_uncommon_ancestors;
662 app.db.get_uncommon_ancestors(left_id, right_id,
663 left_uncommon_ancestors,
664 right_uncommon_ancestors);
665
666 roster_merge_result merge_result;
667 MM(merge_result);
668 roster_merge(*left.first, *left.second, left_uncommon_ancestors,
669 *right.first, *right.second, right_uncommon_ancestors,
670 merge_result);
671
672 revision_id lca_id;
673 database::cached_roster lca;
674 find_common_ancestor_for_merge(left_id, right_id, lca_id, app);
675 app.db.get_roster(lca_id, lca);
676
677 map<file_id, file_path> paths;
678 get_content_paths(working_roster, paths);
679
680 content_merge_workspace_adaptor wca(app, lca.first, paths);
681 resolve_merge_conflicts(*left.first, *right.first, merge_result, wca, app);
682
683 // Make sure it worked...
684 I(merge_result.is_clean());
685 merge_result.roster.check_sane(true);
686
687 // Construct the workspace revision.
688 parent_map parents;
689 safe_insert(parents, std::make_pair(left_id, left));
690 safe_insert(parents, std::make_pair(right_id, right));
691
692 revision_t merged_rev;
693 make_revision_for_workspace(parents, merge_result.roster, merged_rev);
694
695 // Note: the csets in merged_rev are _not_ suitable for submission to
696 // perform_content_update, because content changes have been dropped.
697 cset update;
698 make_cset(*left.first, merge_result.roster, update);
699
700 // small race condition here...
701 app.work.perform_content_update(update, wca);
702 app.work.put_work_rev(merged_rev);
703 app.work.update_any_attrs();
704 app.work.maybe_update_inodeprints();
705
706 P(F("updated to result of merge\n"
707 " [left] %s\n"
708 "[right] %s\n") % left_id % right_id);
709}
710
711CMD(explicit_merge, "explicit_merge", "", CMD_REF(tree),
712 N_("LEFT-REVISION RIGHT-REVISION DEST-BRANCH"),
713 N_("Merges two explicitly given revisions"),
714 N_("The results of the merge are placed on the branch specified by "
715 "DEST-BRANCH."),
716 options::opts::date | options::opts::author)
717{
718 revision_id left, right;
719 branch_name branch;
720
721 if (args.size() != 3)
722 throw usage(execid);
723
724 complete(app, idx(args, 0)(), left);
725 complete(app, idx(args, 1)(), right);
726 branch = branch_name(idx(args, 2)());
727
728 N(!(left == right),
729 F("%s and %s are the same revision, aborting") % left % right);
730 N(!is_ancestor(left, right, app),
731 F("%s is already an ancestor of %s") % left % right);
732 N(!is_ancestor(right, left, app),
733 F("%s is already an ancestor of %s") % right % left);
734
735 merge_two(left, right, branch, string("explicit merge"), app, std::cout, false);
736}
737
738CMD(show_conflicts, "show_conflicts", "", CMD_REF(informative), N_("REV REV"),
739 N_("Shows what conflicts need resolution between two revisions"),
740 N_("The conflicts are calculated based on the two revisions given in "
741 "the REV parameters."),
742 options::opts::branch | options::opts::date | options::opts::author)
743{
744 if (args.size() != 2)
745 throw usage(execid);
746 revision_id l_id, r_id;
747 complete(app, idx(args,0)(), l_id);
748 complete(app, idx(args,1)(), r_id);
749 N(!is_ancestor(l_id, r_id, app),
750 F("%s is an ancestor of %s; no merge is needed.") % l_id % r_id);
751 N(!is_ancestor(r_id, l_id, app),
752 F("%s is an ancestor of %s; no merge is needed.") % r_id % l_id);
753 roster_t l_roster, r_roster;
754 marking_map l_marking, r_marking;
755 app.db.get_roster(l_id, l_roster, l_marking);
756 app.db.get_roster(r_id, r_roster, r_marking);
757 set<revision_id> l_uncommon_ancestors, r_uncommon_ancestors;
758 app.db.get_uncommon_ancestors(l_id, r_id,
759 l_uncommon_ancestors,
760 r_uncommon_ancestors);
761 roster_merge_result result;
762 roster_merge(l_roster, l_marking, l_uncommon_ancestors,
763 r_roster, r_marking, r_uncommon_ancestors,
764 result);
765
766 P(F("There are %s node_name_conflicts.")
767 % result.node_name_conflicts.size());
768 P(F("There are %s file_content_conflicts.")
769 % result.file_content_conflicts.size());
770 P(F("There are %s node_attr_conflicts.")
771 % result.node_attr_conflicts.size());
772 P(F("There are %s orphaned_node_conflicts.")
773 % result.orphaned_node_conflicts.size());
774 P(F("There are %s rename_target_conflicts.")
775 % result.rename_target_conflicts.size());
776 P(F("There are %s directory_loop_conflicts.")
777 % result.directory_loop_conflicts.size());
778}
779
780CMD(pluck, "pluck", "", CMD_REF(workspace), N_("[-r FROM] -r TO [PATH...]"),
781 N_("Applies changes made at arbitrary places in history"),
782 N_("This command takes changes made at any point in history, and "
783 "edits your current workspace to include those changes. The end result "
784 "is identical to 'mtn diff -r FROM -r TO | patch -p0', except that "
785 "this command uses monotone's merger, and thus intelligently handles "
786 "renames, conflicts, and so on.\n"
787 "If one revision is given, applies the changes made in that revision "
788 "compared to its parent.\n"
789 "If two revisions are given, applies the changes made to get from the "
790 "first revision to the second."),
791 options::opts::revision | options::opts::depth | options::opts::exclude)
792{
793 // Work out our arguments
794 revision_id from_rid, to_rid;
795
796 if (app.opts.revision_selectors.size() == 1)
797 {
798 complete(app, idx(app.opts.revision_selectors, 0)(), to_rid);
799 N(app.db.revision_exists(to_rid),
800 F("no such revision '%s'") % to_rid);
801 std::set<revision_id> parents;
802 app.db.get_revision_parents(to_rid, parents);
803 N(parents.size() == 1,
804 F("revision %s is a merge\n"
805 "to apply the changes relative to one of its parents, use:\n"
806 " %s pluck -r PARENT -r %s")
807 % to_rid
808 % ui.prog_name % to_rid);
809 from_rid = *parents.begin();
810 }
811 else if (app.opts.revision_selectors.size() == 2)
812 {
813 complete(app, idx(app.opts.revision_selectors, 0)(), from_rid);
814 N(app.db.revision_exists(from_rid),
815 F("no such revision '%s'") % from_rid);
816 complete(app, idx(app.opts.revision_selectors, 1)(), to_rid);
817 N(app.db.revision_exists(to_rid),
818 F("no such revision '%s'") % to_rid);
819 }
820 else
821 throw usage(execid);
822
823 app.require_workspace();
824
825 N(!(from_rid == to_rid), F("no changes to apply"));
826
827 // notionally, we have the situation
828 //
829 // from --> working
830 // | |
831 // V V
832 // to --> merged
833 //
834 // - from is the revision we start plucking from
835 // - to is the revision we stop plucking at
836 // - working is the current contents of the workspace
837 // - merged is the result of the plucking, and achieved by running a
838 // merge in the fictional graph seen above
839 //
840 // To perform the merge, we use the real from roster, and the real working
841 // roster, but synthesize a temporary 'to' roster. This ensures that the
842 // 'from', 'working' and 'base' rosters all use the same nid namespace,
843 // while any additions that happened between 'from' and 'to' should be
844 // considered as new nodes, even if the file that was added is in fact in
845 // 'working' already -- so 'to' needs its own namespace. (Among other
846 // things, it is impossible with our merge formalism to have the above
847 // graph with a node that exists in 'to' and 'working', but not 'from'.)
848 //
849 // finally, we take the cset from working -> merged, and apply that to the
850 // workspace
851 // and take the cset from the workspace's base, and write that to _MTN/work
852
853 // The node id source we'll use for the 'working' and 'to' rosters.
854 temp_node_id_source nis;
855
856 // Get the FROM roster
857 shared_ptr<roster_t> from_roster = shared_ptr<roster_t>(new roster_t());
858 MM(*from_roster);
859 app.db.get_roster(from_rid, *from_roster);
860
861 // Get the WORKING roster
862 roster_t working_roster; MM(working_roster);
863 app.work.get_current_roster_shape(working_roster, nis);
864
865 app.work.update_current_roster_from_filesystem(working_roster);
866
867 // Get the FROM->TO cset...
868 cset from_to_to; MM(from_to_to);
869 cset from_to_to_excluded; MM(from_to_to_excluded);
870 {
871 roster_t to_true_roster;
872 app.db.get_roster(to_rid, to_true_roster);
873 node_restriction mask(args_to_paths(args),
874 args_to_paths(app.opts.exclude_patterns),
875 app.opts.depth,
876 *from_roster, to_true_roster, app);
877 make_restricted_csets(*from_roster, to_true_roster,
878 from_to_to, from_to_to_excluded,
879 mask);
880 MM(from_to_to);
881 MM(from_to_to_excluded);
882 check_restricted_cset(*from_roster, from_to_to);
883 }
884 N(!from_to_to.empty(), F("no changes to be applied"));
885 // ...and use it to create the TO roster
886 roster_t to_roster; MM(to_roster);
887 {
888 to_roster = *from_roster;
889 editable_roster_base editable_to_roster(to_roster, nis);
890 from_to_to.apply_to(editable_to_roster);
891 }
892
893 // Now do the merge
894 roster_merge_result result;
895 three_way_merge(*from_roster, working_roster, to_roster, result);
896
897 roster_t & merged_roster = result.roster;
898
899 map<file_id, file_path> paths;
900 get_content_paths(working_roster, paths);
901
902 content_merge_workspace_adaptor wca(app, from_roster, paths);
903 resolve_merge_conflicts(working_roster, to_roster,
904 result, wca, app);
905
906 I(result.is_clean());
907 // temporary node ids may appear
908 merged_roster.check_sane(true);
909
910 // we apply the working to merged cset to the workspace
911 cset update;
912 MM(update);
913 make_cset(working_roster, merged_roster, update);
914 E(!update.empty(), F("no changes were applied"));
915 app.work.perform_content_update(update, wca);
916
917 P(F("applied changes to workspace"));
918
919 // and record any remaining changes in _MTN/revision
920 parent_map parents;
921 revision_t remaining;
922 MM(remaining);
923 app.work.get_parent_rosters(parents);
924 make_revision_for_workspace(parents, merged_roster, remaining);
925
926 // small race condition here...
927 app.work.put_work_rev(remaining);
928 app.work.update_any_attrs();
929
930 // add a note to the user log file about what we did
931 {
932 utf8 log;
933 app.work.read_user_log(log);
934 std::string log_str = log();
935 if (!log_str.empty())
936 log_str += "\n";
937 if (from_to_to_excluded.empty())
938 log_str += (FL("applied changes from %s\n"
939 " through %s\n")
940 % from_rid % to_rid).str();
941 else
942 log_str += (FL("applied partial changes from %s\n"
943 " through %s\n")
944 % from_rid % to_rid).str();
945 app.work.write_user_log(utf8(log_str));
946 }
947}
948
949CMD(heads, "heads", "", CMD_REF(tree), "",
950 N_("Shows unmerged head revisions of a branch"),
951 "",
952 options::opts::branch)
953{
954 set<revision_id> heads;
955 if (args.size() != 0)
956 throw usage(execid);
957
958 N(app.opts.branchname() != "",
959 F("please specify a branch, with --branch=BRANCH"));
960
961 app.get_project().get_branch_heads(app.opts.branchname, heads);
962
963 if (heads.size() == 0)
964 P(F("branch '%s' is empty") % app.opts.branchname);
965 else if (heads.size() == 1)
966 P(F("branch '%s' is currently merged:") % app.opts.branchname);
967 else
968 P(F("branch '%s' is currently unmerged:") % app.opts.branchname);
969
970 for (set<revision_id>::const_iterator i = heads.begin();
971 i != heads.end(); ++i)
972 cout << describe_revision(app, *i) << '\n';
973}
974
975CMD(get_roster, "get_roster", "", CMD_REF(debug), N_("[REVID]"),
976 N_("Dumps the roster associated with a given identifier"),
977 N_("If no REVID is given, the workspace is used."),
978 options::opts::none)
979{
980 roster_t roster;
981 marking_map mm;
982
983 if (args.size() == 0)
984 {
985 parent_map parents;
986 temp_node_id_source nis;
987 revision_id rid(fake_id());
988
989 app.require_workspace();
990 app.work.get_parent_rosters(parents);
991 app.work.get_current_roster_shape(roster, nis);
992 app.work.update_current_roster_from_filesystem(roster);
993
994 if (parents.size() == 0)
995 {
996 mark_roster_with_no_parents(rid, roster, mm);
997 }
998 else if (parents.size() == 1)
999 {
1000 roster_t parent = parent_roster(parents.begin());
1001 marking_map parent_mm = parent_marking(parents.begin());
1002 mark_roster_with_one_parent(parent, parent_mm, rid, roster, mm);
1003 }
1004 else
1005 {
1006 parent_map::const_iterator i = parents.begin();
1007 revision_id left_id = parent_id(i);
1008 roster_t const & left_roster = parent_roster(i);
1009 marking_map const & left_markings = parent_marking(i);
1010
1011 i++;
1012 revision_id right_id = parent_id(i);
1013 roster_t const & right_roster = parent_roster(i);
1014 marking_map const & right_markings = parent_marking(i);
1015
1016 i++; I(i == parents.end());
1017
1018 set<revision_id> left_uncommon_ancestors, right_uncommon_ancestors;
1019 app.db.get_uncommon_ancestors(left_id, right_id,
1020 left_uncommon_ancestors,
1021 right_uncommon_ancestors);
1022
1023 mark_merge_roster(left_roster, left_markings,
1024 left_uncommon_ancestors,
1025 right_roster, right_markings,
1026 right_uncommon_ancestors,
1027 rid, roster, mm);
1028 }
1029 }
1030 else if (args.size() == 1)
1031 {
1032 revision_id rid;
1033 complete(app, idx(args, 0)(), rid);
1034 I(!null_id(rid));
1035 app.db.get_roster(rid, roster, mm);
1036 }
1037 else
1038 throw usage(execid);
1039
1040 roster_data dat;
1041 write_roster_and_marking(roster, mm, dat);
1042 cout << dat;
1043}
1044
1045
1046// Local Variables:
1047// mode: C++
1048// fill-column: 76
1049// c-file-style: "gnu"
1050// indent-tabs-mode: nil
1051// End:
1052// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:

Archive Download this file

Branches

Tags

Quick Links:     www.monotone.ca    -     Downloads    -     Documentation    -     Wiki    -     Code Forge    -     Build Status