493
by Martin Pool
- Merge aaron's merge command |
1 |
import changeset |
2 |
from changeset import Inventory, apply_changeset, invert_dict |
|
3 |
import os.path |
|
974.1.8
by Aaron Bentley
Added default backups for merge-revert |
4 |
from osutils import backup_file |
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
5 |
from merge3 import Merge3 |
6 |
||
7 |
class ApplyMerge3: |
|
8 |
"""Contents-change wrapper around merge3.Merge3"""
|
|
974.1.23
by Aaron Bentley
Avoided unnecessary temp files |
9 |
def __init__(self, file_id, base, other): |
10 |
self.file_id = file_id |
|
11 |
self.base = base |
|
12 |
self.other = other |
|
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
13 |
|
14 |
def __eq__(self, other): |
|
15 |
if not isinstance(other, ApplyMerge3): |
|
16 |
return False |
|
974.1.23
by Aaron Bentley
Avoided unnecessary temp files |
17 |
return (self.base == other.base and |
18 |
self.other == other.other and self.file_id == other.file_id) |
|
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
19 |
|
20 |
def __ne__(self, other): |
|
21 |
return not (self == other) |
|
22 |
||
23 |
||
24 |
def apply(self, filename, conflict_handler, reverse=False): |
|
25 |
new_file = filename+".new" |
|
26 |
if not reverse: |
|
974.1.23
by Aaron Bentley
Avoided unnecessary temp files |
27 |
base = self.base |
28 |
other = self.other |
|
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
29 |
else: |
974.1.23
by Aaron Bentley
Avoided unnecessary temp files |
30 |
base = self.other |
31 |
other = self.base |
|
32 |
def get_lines(tree): |
|
33 |
if self.file_id not in tree: |
|
34 |
raise Exception("%s not in tree" % self.file_id) |
|
35 |
return () |
|
36 |
return tree.get_file(self.file_id).readlines() |
|
37 |
base_lines = get_lines(base) |
|
38 |
other_lines = get_lines(other) |
|
39 |
m3 = Merge3(base_lines, file(filename, "rb").readlines(), other_lines) |
|
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
40 |
|
41 |
new_conflicts = False |
|
42 |
output_file = file(new_file, "wb") |
|
43 |
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE" |
|
44 |
for line in m3.merge_lines(name_a = "TREE", name_b = "MERGE-SOURCE", |
|
45 |
start_marker=start_marker): |
|
46 |
if line.startswith(start_marker): |
|
47 |
new_conflicts = True |
|
974.1.45
by aaron.bentley at utoronto
Shortened conflict markers to 7 characters, to please smerge |
48 |
output_file.write(line.replace(start_marker, '<' * 7)) |
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
49 |
else: |
50 |
output_file.write(line) |
|
51 |
output_file.close() |
|
52 |
if not new_conflicts: |
|
53 |
os.chmod(new_file, os.stat(filename).st_mode) |
|
54 |
os.rename(new_file, filename) |
|
55 |
return
|
|
56 |
else: |
|
974.1.23
by Aaron Bentley
Avoided unnecessary temp files |
57 |
conflict_handler.merge_conflict(new_file, filename, base_lines, |
58 |
other_lines) |
|
493
by Martin Pool
- Merge aaron's merge command |
59 |
|
974.1.8
by Aaron Bentley
Added default backups for merge-revert |
60 |
|
61 |
class BackupBeforeChange: |
|
62 |
"""Contents-change wrapper to back up file first"""
|
|
63 |
def __init__(self, contents_change): |
|
64 |
self.contents_change = contents_change |
|
65 |
||
66 |
def __eq__(self, other): |
|
67 |
if not isinstance(other, BackupBeforeChange): |
|
68 |
return False |
|
69 |
return (self.contents_change == other.contents_change) |
|
70 |
||
71 |
def __ne__(self, other): |
|
72 |
return not (self == other) |
|
73 |
||
74 |
def apply(self, filename, conflict_handler, reverse=False): |
|
75 |
backup_file(filename) |
|
76 |
self.contents_change.apply(filename, conflict_handler, reverse) |
|
77 |
||
78 |
||
493
by Martin Pool
- Merge aaron's merge command |
79 |
def invert_invent(inventory): |
80 |
invert_invent = {} |
|
974.1.19
by Aaron Bentley
Removed MergeTree.inventory |
81 |
for file_id in inventory: |
82 |
path = inventory.id2path(file_id) |
|
83 |
if path == '': |
|
84 |
path = './.' |
|
85 |
else: |
|
86 |
path = './' + path |
|
87 |
invert_invent[file_id] = path |
|
493
by Martin Pool
- Merge aaron's merge command |
88 |
return invert_invent |
89 |
||
90 |
||
91 |
def merge_flex(this, base, other, changeset_function, inventory_function, |
|
974.1.17
by Aaron Bentley
Switched to using a set of interesting file_ids instead of SourceFile attribute |
92 |
conflict_handler, merge_factory, interesting_ids): |
974.1.19
by Aaron Bentley
Removed MergeTree.inventory |
93 |
cset = changeset_function(base, other, interesting_ids) |
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
94 |
new_cset = make_merge_changeset(cset, this, base, other, |
974.1.3
by Aaron Bentley
Added merge_factory parameter to merge_flex |
95 |
conflict_handler, merge_factory) |
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
96 |
result = apply_changeset(new_cset, invert_invent(this.tree.inventory), |
622
by Martin Pool
Updated merge patch from Aaron |
97 |
this.root, conflict_handler, False) |
98 |
conflict_handler.finalize() |
|
99 |
return result |
|
493
by Martin Pool
- Merge aaron's merge command |
100 |
|
101 |
||
102 |
||
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
103 |
def make_merge_changeset(cset, this, base, other, |
974.1.3
by Aaron Bentley
Added merge_factory parameter to merge_flex |
104 |
conflict_handler, merge_factory): |
493
by Martin Pool
- Merge aaron's merge command |
105 |
new_cset = changeset.Changeset() |
106 |
def get_this_contents(id): |
|
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
107 |
path = this.readonly_path(id) |
493
by Martin Pool
- Merge aaron's merge command |
108 |
if os.path.isdir(path): |
109 |
return changeset.dir_create |
|
110 |
else: |
|
111 |
return changeset.FileCreate(file(path, "rb").read()) |
|
112 |
||
113 |
for entry in cset.entries.itervalues(): |
|
114 |
if entry.is_boring(): |
|
115 |
new_cset.add_entry(entry) |
|
116 |
else: |
|
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
117 |
new_entry = make_merged_entry(entry, this, base, other, |
118 |
conflict_handler) |
|
909.1.4
by Aaron Bentley
Fixed conflict handling for missing merge targets |
119 |
new_contents = make_merged_contents(entry, this, base, other, |
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
120 |
conflict_handler, |
974.1.3
by Aaron Bentley
Added merge_factory parameter to merge_flex |
121 |
merge_factory) |
850
by Martin Pool
- Merge merge updates from aaron |
122 |
new_entry.contents_change = new_contents |
123 |
new_entry.metadata_change = make_merged_metadata(entry, base, other) |
|
124 |
new_cset.add_entry(new_entry) |
|
125 |
||
493
by Martin Pool
- Merge aaron's merge command |
126 |
return new_cset |
127 |
||
974.1.22
by Aaron Bentley
Refactored code |
128 |
class ThreeWayConflict(Exception): |
129 |
def __init__(self, this, base, other): |
|
130 |
self.this = this |
|
131 |
self.base = base |
|
132 |
self.other = other |
|
133 |
msg = "Conflict merging %s %s and %s" % (this, base, other) |
|
134 |
Exception.__init__(self, msg) |
|
135 |
||
136 |
def threeway_select(this, base, other): |
|
137 |
"""Returns a value selected by the three-way algorithm.
|
|
138 |
Raises ThreewayConflict if the algorithm yields a conflict"""
|
|
139 |
if base == other: |
|
140 |
return this |
|
141 |
elif base == this: |
|
142 |
return other |
|
143 |
elif other == this: |
|
144 |
return this |
|
145 |
else: |
|
146 |
raise ThreeWayConflict(this, base, other) |
|
147 |
||
148 |
||
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
149 |
def make_merged_entry(entry, this, base, other, conflict_handler): |
909.1.2
by aaron.bentley at utoronto
Fixed rename handling in merge |
150 |
from bzrlib.trace import mutter |
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
151 |
def entry_data(file_id, tree): |
152 |
assert hasattr(tree, "__contains__"), "%s" % tree |
|
974.1.47
by Aaron Bentley
Merged changes from the merge4 branch |
153 |
if not tree.has_or_had_id(file_id): |
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
154 |
return (None, None, "") |
155 |
entry = tree.tree.inventory[file_id] |
|
156 |
my_dir = tree.id2path(entry.parent_id) |
|
157 |
if my_dir is None: |
|
158 |
my_dir = "" |
|
159 |
return entry.name, entry.parent_id, my_dir |
|
160 |
this_name, this_parent, this_dir = entry_data(entry.id, this) |
|
161 |
base_name, base_parent, base_dir = entry_data(entry.id, base) |
|
162 |
other_name, other_parent, other_dir = entry_data(entry.id, other) |
|
909.1.2
by aaron.bentley at utoronto
Fixed rename handling in merge |
163 |
mutter("Dirs: this, base, other %r %r %r" % (this_dir, base_dir, other_dir)) |
164 |
mutter("Names: this, base, other %r %r %r" % (this_name, base_name, other_name)) |
|
974.1.22
by Aaron Bentley
Refactored code |
165 |
old_name = this_name |
166 |
try: |
|
167 |
new_name = threeway_select(this_name, base_name, other_name) |
|
168 |
except ThreeWayConflict: |
|
169 |
new_name = conflict_handler.rename_conflict(entry.id, this_name, |
|
170 |
base_name, other_name) |
|
171 |
||
172 |
old_parent = this_parent |
|
173 |
try: |
|
174 |
new_parent = threeway_select(this_parent, base_parent, other_parent) |
|
175 |
except ThreeWayConflict: |
|
176 |
new_parent = conflict_handler.move_conflict(entry.id, this_dir, |
|
177 |
base_dir, other_dir) |
|
178 |
def get_path(name, parent): |
|
974.1.47
by Aaron Bentley
Merged changes from the merge4 branch |
179 |
if name is not None: |
180 |
if name == "": |
|
181 |
assert parent is None |
|
182 |
return './.' |
|
974.1.22
by Aaron Bentley
Refactored code |
183 |
parent_dir = {this_parent: this_dir, other_parent: other_dir, |
184 |
base_parent: base_dir} |
|
185 |
directory = parent_dir[parent] |
|
186 |
return os.path.join(directory, name) |
|
187 |
else: |
|
974.1.47
by Aaron Bentley
Merged changes from the merge4 branch |
188 |
assert parent is None |
974.1.22
by Aaron Bentley
Refactored code |
189 |
return None |
190 |
||
191 |
old_path = get_path(old_name, old_parent) |
|
192 |
||
909.1.2
by aaron.bentley at utoronto
Fixed rename handling in merge |
193 |
new_entry = changeset.ChangesetEntry(entry.id, old_parent, old_path) |
974.1.22
by Aaron Bentley
Refactored code |
194 |
new_entry.new_path = get_path(new_name, new_parent) |
493
by Martin Pool
- Merge aaron's merge command |
195 |
new_entry.new_parent = new_parent |
909.1.2
by aaron.bentley at utoronto
Fixed rename handling in merge |
196 |
mutter(repr(new_entry)) |
850
by Martin Pool
- Merge merge updates from aaron |
197 |
return new_entry |
198 |
||
199 |
||
974.1.47
by Aaron Bentley
Merged changes from the merge4 branch |
200 |
def get_contents(entry, tree): |
201 |
"""Get a contents change element suitable for use with ReplaceContents
|
|
202 |
"""
|
|
203 |
tree_entry = tree.tree.inventory[entry.id] |
|
204 |
if tree_entry.kind == "file": |
|
205 |
return changeset.FileCreate(tree.get_file(entry.id).read()) |
|
206 |
else: |
|
207 |
assert tree_entry.kind in ("root_directory", "directory") |
|
208 |
return changeset.dir_create |
|
209 |
||
210 |
||
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
211 |
def make_merged_contents(entry, this, base, other, conflict_handler, |
974.1.3
by Aaron Bentley
Added merge_factory parameter to merge_flex |
212 |
merge_factory): |
850
by Martin Pool
- Merge merge updates from aaron |
213 |
contents = entry.contents_change |
214 |
if contents is None: |
|
215 |
return None |
|
216 |
this_path = this.readonly_path(entry.id) |
|
974.1.3
by Aaron Bentley
Added merge_factory parameter to merge_flex |
217 |
def make_merge(): |
850
by Martin Pool
- Merge merge updates from aaron |
218 |
if this_path is None: |
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
219 |
return conflict_handler.missing_for_merge(entry.id, |
220 |
other.id2path(entry.id)) |
|
974.1.23
by Aaron Bentley
Avoided unnecessary temp files |
221 |
return merge_factory(entry.id, base, other) |
850
by Martin Pool
- Merge merge updates from aaron |
222 |
|
223 |
if isinstance(contents, changeset.ReplaceContents): |
|
224 |
if contents.old_contents is None and contents.new_contents is None: |
|
225 |
return None |
|
226 |
if contents.new_contents is None: |
|
227 |
if this_path is not None and os.path.exists(this_path): |
|
228 |
return contents |
|
229 |
else: |
|
230 |
return None |
|
231 |
elif contents.old_contents is None: |
|
232 |
if this_path is None or not os.path.exists(this_path): |
|
233 |
return contents |
|
234 |
else: |
|
974.1.47
by Aaron Bentley
Merged changes from the merge4 branch |
235 |
this_contents = get_contents(entry, this) |
850
by Martin Pool
- Merge merge updates from aaron |
236 |
if this_contents == contents.new_contents: |
237 |
return None |
|
238 |
else: |
|
239 |
other_path = other.readonly_path(entry.id) |
|
240 |
conflict_handler.new_contents_conflict(this_path, |
|
241 |
other_path) |
|
242 |
elif isinstance(contents.old_contents, changeset.FileCreate) and \ |
|
243 |
isinstance(contents.new_contents, changeset.FileCreate): |
|
974.1.3
by Aaron Bentley
Added merge_factory parameter to merge_flex |
244 |
return make_merge() |
850
by Martin Pool
- Merge merge updates from aaron |
245 |
else: |
246 |
raise Exception("Unhandled merge scenario") |
|
247 |
||
248 |
def make_merged_metadata(entry, base, other): |
|
249 |
if entry.metadata_change is not None: |
|
250 |
base_path = base.readonly_path(entry.id) |
|
251 |
other_path = other.readonly_path(entry.id) |
|
252 |
return PermissionsMerge(base_path, other_path) |
|
493
by Martin Pool
- Merge aaron's merge command |
253 |
|
254 |
||
558
by Martin Pool
- All top-level classes inherit from object |
255 |
class PermissionsMerge(object): |
493
by Martin Pool
- Merge aaron's merge command |
256 |
def __init__(self, base_path, other_path): |
257 |
self.base_path = base_path |
|
258 |
self.other_path = other_path |
|
259 |
||
260 |
def apply(self, filename, conflict_handler, reverse=False): |
|
261 |
if not reverse: |
|
262 |
base = self.base_path |
|
263 |
other = self.other_path |
|
264 |
else: |
|
265 |
base = self.other_path |
|
266 |
other = self.base_path |
|
267 |
base_stat = os.stat(base).st_mode |
|
268 |
other_stat = os.stat(other).st_mode |
|
269 |
this_stat = os.stat(filename).st_mode |
|
270 |
if base_stat &0777 == other_stat &0777: |
|
271 |
return
|
|
272 |
elif this_stat &0777 == other_stat &0777: |
|
273 |
return
|
|
274 |
elif this_stat &0777 == base_stat &0777: |
|
275 |
os.chmod(filename, other_stat) |
|
276 |
else: |
|
277 |
conflict_handler.permission_conflict(filename, base, other) |