18
18
from bzrlib.config import Config
19
from bzrlib.errors import (BzrError, NoSuchFile, UnknownFormatError,
20
NoCommonAncestor, UnrelatedBranches)
19
from bzrlib.errors import (BzrError, NoSuchFile, UnknownFormatError,
20
NoCommonAncestor, NoSuchId, UnrelatedBranches)
21
21
from bzrlib.generate_ids import gen_revision_id
22
from bzrlib.graph import FrozenHeadsCache
22
23
from bzrlib.merge import Merger
23
24
from bzrlib import osutils
24
25
from bzrlib.revision import NULL_REVISION
25
26
from bzrlib.trace import mutter
27
from bzrlib.tsort import topo_sort
26
28
import bzrlib.ui as ui
28
30
from maptree import MapTree, map_file_ids
124
127
return gen_revision_id(rev.committer, rev.timestamp)
127
def generate_simple_plan(history, start_revid, stop_revid, onto_revid,
128
onto_ancestry, get_parents, generate_revid,
129
skip_full_merged=False):
130
def generate_simple_plan(todo_set, start_revid, stop_revid, onto_revid, graph,
131
generate_revid, skip_full_merged=False):
130
132
"""Create a simple rebase plan that replays history based
131
133
on one revision being replayed on top of another.
133
:param history: Revision history
135
:param todo_set: A set of revisions to rebase. Only the revisions from
136
stop_revid back through the left hand ancestry are rebased; other
137
revisions are ignored (and references to them are preserved).
134
138
:param start_revid: Id of revision at which to start replaying
135
139
:param stop_revid: Id of revision until which to stop replaying
136
140
:param onto_revid: Id of revision on top of which to replay
137
:param onto_ancestry: Ancestry of onto_revid
138
:param get_parents: Function for obtaining the parents of a revision
141
:param graph: Graph object
139
142
:param generate_revid: Function for generating new revision ids
140
143
:param skip_full_merged: Skip revisions that merge already merged
143
146
:return: replace map
145
assert start_revid is None or start_revid in history
146
assert stop_revid is None or stop_revid in history
148
assert start_revid is None or start_revid in todo_set, \
149
"invalid start revid(%r), todo_set(%r)" % (start_revid, todo_set)
150
assert stop_revid is None or stop_revid in todo_set, "invalid stop_revid"
148
if start_revid is not None:
149
start_revno = history.index(start_revid)
152
if stop_revid is not None:
153
stop_revno = history.index(stop_revid)+1
152
parent_map = graph.get_parent_map(todo_set)
153
order = topo_sort(parent_map)
155
if stop_revid is None:
156
stop_revid = order[-1]
158
while rev in parent_map:
159
left_most_path.append(rev)
160
if rev == start_revid:
161
# manual specified early-stop
163
rev = parent_map[rev][0]
164
left_most_path.reverse()
165
if start_revid is None:
166
# We need a common base.
167
lca = graph.find_lca(stop_revid, onto_revid)
168
if lca == set([NULL_REVISION]):
169
raise UnrelatedBranches()
156
170
new_parent = onto_revid
157
for oldrevid in history[start_revno:stop_revno]:
158
oldparents = list(get_parents(oldrevid))
159
assert isinstance(oldparents, list)
160
assert oldparents == [] or \
161
oldparents[0] == history[history.index(oldrevid)-1]
171
todo = left_most_path
172
heads_cache = FrozenHeadsCache(graph)
173
# XXX: The output replacemap'd parents should get looked up in some manner
174
# by the heads cache? RBC 20080719
175
for oldrevid in todo:
176
oldparents = parent_map[oldrevid]
177
assert isinstance(oldparents, tuple), "not tuple: %r" % oldparents
162
178
if len(oldparents) > 1:
163
parents = [new_parent] + filter(lambda p: p not in onto_ancestry or p == onto_revid, oldparents[1:])
179
additional_parents = heads_cache.heads(oldparents[1:])
180
parents = [new_parent]
181
for parent in parents:
182
if parent in additional_parents and parent not in parents:
184
parent = replace_map.get(parent, (parent,))[0]
185
parents.append(parent)
186
parents = tuple(parents)
164
187
if len(parents) == 1 and skip_full_merged:
167
parents = [new_parent]
190
parents = (new_parent,)
168
191
newrevid = generate_revid(oldrevid)
169
assert newrevid != oldrevid
192
assert newrevid != oldrevid, "old and newrevid equal (%r)" % newrevid
193
assert isinstance(parents, tuple), "parents not tuple: %r" % parents
170
194
replace_map[oldrevid] = (newrevid, parents)
171
195
new_parent = newrevid
172
196
return replace_map
175
def generate_transpose_plan(graph, renames, get_parents, generate_revid):
199
def generate_transpose_plan(ancestry, renames, graph, generate_revid):
176
200
"""Create a rebase plan that replaces a bunch of revisions
177
201
in a revision graph.
179
:param graph: Revision graph in which to operate
203
:param ancestry: Ancestry to consider
180
204
:param renames: Renames of revision
181
:param get_parents: Function for determining parents
205
:param graph: Graph object
182
206
:param generate_revid: Function for creating new revision ids
188
if not children.has_key(r):
212
for r, ps in ancestry:
213
if not children.has_key(r):
215
if ps is None: # Ghost
218
if not children.has_key(r):
191
221
if not children.has_key(p):
193
223
children[p].append(r)
225
parent_map.update(graph.get_parent_map(filter(lambda x: not x in parent_map, renames.values())))
195
227
# todo contains a list of revisions that need to
198
replace_map[r] = (renames[r], get_parents(renames[r]))
229
for r, v in renames.items():
230
replace_map[r] = (v, parent_map[v])
201
233
total = len(todo)
214
247
if replace_map.has_key(c):
215
parents = list(replace_map[c][1])
248
parents = replace_map[c][1]
217
parents = list(graph[c])
218
assert isinstance(parents, list), \
219
"Expected list of parents, got: %r" % parents
250
parents = parent_map[c]
251
assert isinstance(parents, tuple), \
252
"Expected tuple of parents, got: %r" % parents
220
253
# replace r in parents with replace_map[r][0]
221
254
if not replace_map[r][0] in parents:
255
parents = list(parents)
222
256
parents[parents.index(r)] = replace_map[r][0]
223
replace_map[c] = (generate_revid(c), parents)
224
assert replace_map[c][0] != c
227
todo.extend(filter(lambda x: not x in processed, children[r]))
257
parents = tuple(parents)
258
replace_map[c] = (generate_revid(c), tuple(parents))
259
if replace_map[c][0] == c:
261
elif c not in processed:
254
290
:param replace_map: Dictionary with revisions to (optionally) rewrite
255
291
:param merge_fn: Function for replaying a revision
257
todo = list(rebase_todo(repository, replace_map))
260
293
# Figure out the dependencies
262
for p in replace_map[revid][1]:
263
if repository.has_revision(p):
265
if not dependencies.has_key(p):
267
dependencies[p].append(revid)
294
graph = repository.get_graph()
295
todo = list(graph.iter_topo_order(replace_map.keys()))
269
296
pb = ui.ui_factory.nested_progress_bar()
274
pb.update('rebase revisions', i, total)
298
for i, revid in enumerate(todo):
299
pb.update('rebase revisions', i, len(todo))
277
300
(newrevid, newparents) = replace_map[revid]
278
if filter(repository.has_revision, newparents) != newparents:
279
# Not all parents present yet, avoid for now
301
assert isinstance(newparents, tuple), "Expected tuple for %r" % newparents
281
302
if repository.has_revision(newrevid):
282
303
# Was already converted, no need to worry about it again
284
305
replay_fn(repository, revid, newrevid, newparents)
285
assert repository.has_revision(newrevid)
286
assert list(repository.revision_parents(newrevid)) == newparents, \
287
"expected parents %r, got %r" % (newparents,
288
repository.revision_parents(newrevid))
289
if dependencies.has_key(newrevid):
290
todo.extend(dependencies[newrevid])
291
del dependencies[newrevid]
295
#assert all(map(repository.has_revision,
296
# [replace_map[r][0] for r in replace_map]))
300
def replay_snapshot(repository, oldrevid, newrevid, new_parents,
301
revid_renames, fix_revid=None):
310
def replay_snapshot(repository, oldrevid, newrevid, new_parents):
302
311
"""Replay a commit by simply commiting the same snapshot with different
328
336
# Check what new_ie.file_id should be
329
337
# use old and new parent inventories to generate new_id map
330
fileid_map = map_file_ids(repository, oldrev.parent_ids, new_parents)
331
oldtree = MapTree(repository.revision_tree(oldrevid), fileid_map)
332
total = len(oldtree.inventory)
338
nonghost_oldparents = tuple([p for p in oldrev.parent_ids if repository.has_revision(p)])
339
nonghost_newparents = tuple([p for p in new_parents if repository.has_revision(p)])
340
fileid_map = map_file_ids(repository, nonghost_oldparents, nonghost_newparents)
341
oldtree = repository.revision_tree(oldrevid)
342
mappedtree = MapTree(oldtree, fileid_map)
333
343
pb = ui.ui_factory.nested_progress_bar()
336
parent_invs = map(repository.get_revision_inventory, new_parents)
337
transact = repository.get_transaction()
338
for path, ie in oldtree.inventory.iter_entries():
339
pb.update('upgrading file', i, total)
345
old_parent_invs = list(repository.iter_inventories(nonghost_oldparents))
346
new_parent_invs = list(repository.iter_inventories(nonghost_newparents))
347
for i, (path, old_ie) in enumerate(mappedtree.inventory.iter_entries()):
348
pb.update('upgrading file', i, len(mappedtree.inventory))
341
350
# Either this file was modified last in this revision,
342
351
# in which case it has to be rewritten
343
if fix_revid is not None:
344
ie.revision = fix_revid(ie.revision)
345
if ie.revision == oldrevid:
346
if repository.weave_store.get_weave_or_empty(ie.file_id,
347
repository.get_transaction()).has_version(newrevid):
352
if old_ie.revision == oldrevid:
353
if repository.texts.has_key((ie.file_id, newrevid)):
354
# Use the existing text
348
355
ie.revision = newrevid
350
358
ie.revision = None
352
360
# or it was already there before the commit, in
353
361
# which case the right revision should be used
354
if revid_renames.has_key(ie.revision):
355
ie.revision = revid_renames[ie.revision]
356
# make sure at least one of the new parents contains
357
# the ie.file_id, ie.revision combination
358
if len(filter(lambda inv: ie.file_id in inv and inv[ie.file_id].revision == ie.revision, parent_invs)) == 0:
359
raise ReplayParentsInconsistent(ie.file_id, ie.revision)
361
builder.record_entry_contents(ie, parent_invs, path, oldtree,
362
oldtree.path_content_summary(path))
362
# one of the old parents had this revision, so find that
363
# and then use the matching new parent
364
old_file_id = oldtree.inventory.path2id(path)
365
assert old_file_id is not None
367
for (old_pinv, new_pinv) in zip(old_parent_invs, new_parent_invs):
368
if (old_pinv.has_id(old_file_id) and
369
old_pinv[old_file_id].revision == old_ie.revision):
371
ie = new_pinv[old_ie.file_id].copy()
373
raise ReplayParentsInconsistent(old_ie.file_id, old_ie.revision)
375
assert ie is not None
376
builder.record_entry_contents(ie, new_parent_invs, path, mappedtree,
377
mappedtree.path_content_summary(path))
365
380
builder.finish_inventory()
375
390
:param wt: Mutable tree with the changes.
376
391
:param oldrev: Revision info of new revision to commit.
377
392
:param newrevid: New revision id."""
378
assert oldrev.revision_id != newrevid
393
assert oldrev.revision_id != newrevid, "Invalid revid %r" % newrevid
379
394
revprops = dict(oldrev.properties)
380
395
revprops[REVPROP_REBASE_OF] = oldrev.revision_id
396
committer = wt.branch.get_config().username()
397
author = oldrev.get_apparent_author()
398
if author == committer or 'author' in revprops:
381
400
wt.commit(message=oldrev.message, timestamp=oldrev.timestamp,
382
timezone=oldrev.timezone, revprops=revprops, rev_id=newrevid)
401
timezone=oldrev.timezone, revprops=revprops, rev_id=newrevid,
402
committer=committer, author=author)
383
403
write_active_rebase_revid(wt, None)
386
406
def replay_determine_base(graph, oldrevid, oldparents, newrevid, newparents):
407
"""Determine the base for replaying a revision using merge.
409
:param graph: Revision graph.
410
:param oldrevid: Revid of old revision.
411
:param oldparents: List of old parents revids.
412
:param newrevid: Revid of new revision.
413
:param newparents: List of new parents revids.
414
:return: Revision id of the new new revision.
387
416
# If this was the first commit, no base is needed
388
417
if len(oldparents) == 0:
389
418
return NULL_REVISION
423
452
oldrev = wt.branch.repository.get_revision(oldrevid)
424
453
# Make sure there are no conflicts or pending merges/changes
425
454
# in the working tree
426
if wt.changes_from(wt.basis_tree()).has_changed():
427
raise BzrError("Working tree has uncommitted changes.")
428
455
complete_revert(wt, [newparents[0]])
429
assert not wt.changes_from(wt.basis_tree()).has_changed()
456
assert not wt.changes_from(wt.basis_tree()).has_changed(), "Changes in rev"
431
458
oldtree = repository.revision_tree(oldrevid)
432
459
write_active_rebase_revid(wt, oldrevid)
433
460
merger = Merger(wt.branch, this_tree=wt)
434
461
merger.set_other_revision(oldrevid, wt.branch)
435
base_revid = replay_determine_base(repository.get_graph(),
462
base_revid = replay_determine_base(repository.get_graph(),
436
463
oldrevid, oldrev.parent_ids,
437
464
newrevid, newparents)
438
mutter('replaying %r as %r with base %r and new parents %r' %
465
mutter('replaying %r as %r with base %r and new parents %r' %
439
466
(oldrevid, newrevid, base_revid, newparents))
440
467
merger.set_base_revision(base_revid, wt.branch)
441
468
merger.merge_type = merge_type