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