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 |
|
1185.31.32
by John Arbash Meinel
Updated the bzr sourcecode to use bzrlib.osutils.pathjoin rather than os.path.join to enforce internal use of / instead of \ |
5 |
from bzrlib.osutils import backup_file, rename, pathjoin |
1092.2.24
by Robert Collins
merge from martins newformat branch - brings in transport abstraction |
6 |
from bzrlib.merge3 import Merge3 |
7 |
import bzrlib |
|
1185.12.83
by Aaron Bentley
Preliminary weave merge support |
8 |
from bzrlib.atomicfile import AtomicFile |
1185.12.30
by Aaron Bentley
Moved all fooCreate into get_contents |
9 |
from changeset import get_contents |
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
10 |
|
11 |
class ApplyMerge3: |
|
1185.12.83
by Aaron Bentley
Preliminary weave merge support |
12 |
history_based = False |
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
13 |
"""Contents-change wrapper around merge3.Merge3"""
|
1185.24.3
by Aaron Bentley
Integrated reprocessing into the rest of the merge stuff |
14 |
def __init__(self, file_id, base, other, show_base=False, reprocess=False): |
974.1.23
by Aaron Bentley
Avoided unnecessary temp files |
15 |
self.file_id = file_id |
16 |
self.base = base |
|
17 |
self.other = other |
|
1185.18.1
by Aaron Bentley
Added --show-base to merge |
18 |
self.show_base = show_base |
1185.24.3
by Aaron Bentley
Integrated reprocessing into the rest of the merge stuff |
19 |
self.reprocess = reprocess |
1185.12.31
by Aaron Bentley
Replaced FileCreate with TreeFileCreate, fixed revert with empty parents |
20 |
|
21 |
def is_creation(self): |
|
22 |
return False |
|
23 |
||
24 |
def is_deletion(self): |
|
25 |
return False |
|
26 |
||
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
27 |
def __eq__(self, other): |
28 |
if not isinstance(other, ApplyMerge3): |
|
29 |
return False |
|
974.1.23
by Aaron Bentley
Avoided unnecessary temp files |
30 |
return (self.base == other.base and |
31 |
self.other == other.other and self.file_id == other.file_id) |
|
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
32 |
|
33 |
def __ne__(self, other): |
|
34 |
return not (self == other) |
|
35 |
||
36 |
def apply(self, filename, conflict_handler, reverse=False): |
|
37 |
new_file = filename+".new" |
|
38 |
if not reverse: |
|
974.1.23
by Aaron Bentley
Avoided unnecessary temp files |
39 |
base = self.base |
40 |
other = self.other |
|
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
41 |
else: |
974.1.23
by Aaron Bentley
Avoided unnecessary temp files |
42 |
base = self.other |
43 |
other = self.base |
|
44 |
def get_lines(tree): |
|
45 |
if self.file_id not in tree: |
|
46 |
raise Exception("%s not in tree" % self.file_id) |
|
47 |
return () |
|
48 |
return tree.get_file(self.file_id).readlines() |
|
49 |
base_lines = get_lines(base) |
|
50 |
other_lines = get_lines(other) |
|
51 |
m3 = Merge3(base_lines, file(filename, "rb").readlines(), other_lines) |
|
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
52 |
|
53 |
new_conflicts = False |
|
54 |
output_file = file(new_file, "wb") |
|
55 |
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE" |
|
1185.18.1
by Aaron Bentley
Added --show-base to merge |
56 |
if self.show_base is True: |
57 |
base_marker = '|' * 7 |
|
58 |
else: |
|
59 |
base_marker = None |
|
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
60 |
for line in m3.merge_lines(name_a = "TREE", name_b = "MERGE-SOURCE", |
1185.18.1
by Aaron Bentley
Added --show-base to merge |
61 |
name_base = "BASE-REVISION", |
1185.24.3
by Aaron Bentley
Integrated reprocessing into the rest of the merge stuff |
62 |
start_marker=start_marker, base_marker=base_marker, |
63 |
reprocess = self.reprocess): |
|
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
64 |
if line.startswith(start_marker): |
65 |
new_conflicts = True |
|
974.1.45
by aaron.bentley at utoronto
Shortened conflict markers to 7 characters, to please smerge |
66 |
output_file.write(line.replace(start_marker, '<' * 7)) |
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
67 |
else: |
68 |
output_file.write(line) |
|
69 |
output_file.close() |
|
70 |
if not new_conflicts: |
|
71 |
os.chmod(new_file, os.stat(filename).st_mode) |
|
1185.1.40
by Robert Collins
Merge what applied of Alexander Belchenko's win32 patch. |
72 |
rename(new_file, filename) |
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
73 |
return
|
74 |
else: |
|
974.1.23
by Aaron Bentley
Avoided unnecessary temp files |
75 |
conflict_handler.merge_conflict(new_file, filename, base_lines, |
76 |
other_lines) |
|
493
by Martin Pool
- Merge aaron's merge command |
77 |
|
1185.12.83
by Aaron Bentley
Preliminary weave merge support |
78 |
class WeaveMerge: |
79 |
"""Contents-change wrapper around weave merge"""
|
|
80 |
history_based = True |
|
81 |
def __init__(self, weave, this_revision_id, other_revision_id): |
|
82 |
self.weave = weave |
|
83 |
self.this_revision_id = this_revision_id |
|
84 |
self.other_revision_id = other_revision_id |
|
85 |
||
86 |
def is_creation(self): |
|
87 |
return False |
|
88 |
||
89 |
def is_deletion(self): |
|
90 |
return False |
|
91 |
||
92 |
def __eq__(self, other): |
|
93 |
if not isinstance(other, WeaveMerge): |
|
94 |
return False |
|
95 |
return self.weave == other.weave and\ |
|
96 |
self.this_revision_id == other.this_revision_id and\ |
|
97 |
self.other_revision_id == other.other_revision_id |
|
98 |
||
99 |
def __ne__(self, other): |
|
100 |
return not (self == other) |
|
101 |
||
102 |
def apply(self, filename, conflict_handler, reverse=False): |
|
103 |
this_i = self.weave.lookup(self.this_revision_id) |
|
104 |
other_i = self.weave.lookup(self.other_revision_id) |
|
105 |
plan = self.weave.plan_merge(this_i, other_i) |
|
106 |
lines = self.weave.weave_merge(plan) |
|
107 |
conflicts = False |
|
108 |
out_file = AtomicFile(filename, mode='wb') |
|
109 |
for line in lines: |
|
1185.12.86
by Aaron Bentley
Switched to standard 7-char conflict markers |
110 |
if line == '<<<<<<<\n': |
1185.12.83
by Aaron Bentley
Preliminary weave merge support |
111 |
conflicts = True |
112 |
out_file.write(line) |
|
1185.12.85
by Aaron Bentley
Added conflict handling for weave merges |
113 |
if conflicts: |
114 |
conflict_handler.weave_merge_conflict(filename, self.weave, |
|
115 |
other_i, out_file) |
|
116 |
else: |
|
117 |
out_file.commit() |
|
974.1.8
by Aaron Bentley
Added default backups for merge-revert |
118 |
|
119 |
class BackupBeforeChange: |
|
120 |
"""Contents-change wrapper to back up file first"""
|
|
121 |
def __init__(self, contents_change): |
|
122 |
self.contents_change = contents_change |
|
1185.12.31
by Aaron Bentley
Replaced FileCreate with TreeFileCreate, fixed revert with empty parents |
123 |
|
124 |
def is_creation(self): |
|
125 |
return self.contents_change.is_creation() |
|
126 |
||
127 |
def is_deletion(self): |
|
128 |
return self.contents_change.is_deletion() |
|
129 |
||
974.1.8
by Aaron Bentley
Added default backups for merge-revert |
130 |
def __eq__(self, other): |
131 |
if not isinstance(other, BackupBeforeChange): |
|
132 |
return False |
|
133 |
return (self.contents_change == other.contents_change) |
|
134 |
||
135 |
def __ne__(self, other): |
|
136 |
return not (self == other) |
|
137 |
||
138 |
def apply(self, filename, conflict_handler, reverse=False): |
|
139 |
backup_file(filename) |
|
140 |
self.contents_change.apply(filename, conflict_handler, reverse) |
|
141 |
||
142 |
||
493
by Martin Pool
- Merge aaron's merge command |
143 |
def invert_invent(inventory): |
144 |
invert_invent = {} |
|
974.1.19
by Aaron Bentley
Removed MergeTree.inventory |
145 |
for file_id in inventory: |
146 |
path = inventory.id2path(file_id) |
|
147 |
if path == '': |
|
148 |
path = './.' |
|
149 |
else: |
|
150 |
path = './' + path |
|
151 |
invert_invent[file_id] = path |
|
493
by Martin Pool
- Merge aaron's merge command |
152 |
return invert_invent |
153 |
||
154 |
||
155 |
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 |
156 |
conflict_handler, merge_factory, interesting_ids): |
974.1.19
by Aaron Bentley
Removed MergeTree.inventory |
157 |
cset = changeset_function(base, other, interesting_ids) |
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
158 |
new_cset = make_merge_changeset(cset, this, base, other, |
974.1.3
by Aaron Bentley
Added merge_factory parameter to merge_flex |
159 |
conflict_handler, merge_factory) |
1185.12.40
by abentley
Got even closer to standard Tree interface |
160 |
result = apply_changeset(new_cset, invert_invent(this.inventory), |
1185.12.38
by abentley
semi-broke merge |
161 |
this.basedir, conflict_handler, False) |
622
by Martin Pool
Updated merge patch from Aaron |
162 |
return result |
493
by Martin Pool
- Merge aaron's merge command |
163 |
|
164 |
||
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
165 |
def make_merge_changeset(cset, this, base, other, |
974.1.3
by Aaron Bentley
Added merge_factory parameter to merge_flex |
166 |
conflict_handler, merge_factory): |
493
by Martin Pool
- Merge aaron's merge command |
167 |
new_cset = changeset.Changeset() |
168 |
||
169 |
for entry in cset.entries.itervalues(): |
|
170 |
if entry.is_boring(): |
|
171 |
new_cset.add_entry(entry) |
|
172 |
else: |
|
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
173 |
new_entry = make_merged_entry(entry, this, base, other, |
174 |
conflict_handler) |
|
909.1.4
by Aaron Bentley
Fixed conflict handling for missing merge targets |
175 |
new_contents = make_merged_contents(entry, this, base, other, |
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
176 |
conflict_handler, |
974.1.3
by Aaron Bentley
Added merge_factory parameter to merge_flex |
177 |
merge_factory) |
850
by Martin Pool
- Merge merge updates from aaron |
178 |
new_entry.contents_change = new_contents |
179 |
new_entry.metadata_change = make_merged_metadata(entry, base, other) |
|
180 |
new_cset.add_entry(new_entry) |
|
181 |
||
493
by Martin Pool
- Merge aaron's merge command |
182 |
return new_cset |
183 |
||
974.1.22
by Aaron Bentley
Refactored code |
184 |
class ThreeWayConflict(Exception): |
185 |
def __init__(self, this, base, other): |
|
186 |
self.this = this |
|
187 |
self.base = base |
|
188 |
self.other = other |
|
189 |
msg = "Conflict merging %s %s and %s" % (this, base, other) |
|
190 |
Exception.__init__(self, msg) |
|
191 |
||
192 |
def threeway_select(this, base, other): |
|
193 |
"""Returns a value selected by the three-way algorithm.
|
|
194 |
Raises ThreewayConflict if the algorithm yields a conflict"""
|
|
195 |
if base == other: |
|
196 |
return this |
|
197 |
elif base == this: |
|
198 |
return other |
|
199 |
elif other == this: |
|
200 |
return this |
|
201 |
else: |
|
202 |
raise ThreeWayConflict(this, base, other) |
|
203 |
||
204 |
||
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
205 |
def make_merged_entry(entry, this, base, other, conflict_handler): |
909.1.2
by aaron.bentley at utoronto
Fixed rename handling in merge |
206 |
from bzrlib.trace import mutter |
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
207 |
def entry_data(file_id, tree): |
208 |
assert hasattr(tree, "__contains__"), "%s" % tree |
|
1185.12.39
by abentley
Propogated has_or_had_id to Tree |
209 |
if not tree.has_or_had_id(file_id): |
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
210 |
return (None, None, "") |
1185.12.40
by abentley
Got even closer to standard Tree interface |
211 |
entry = tree.inventory[file_id] |
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
212 |
my_dir = tree.id2path(entry.parent_id) |
213 |
if my_dir is None: |
|
214 |
my_dir = "" |
|
215 |
return entry.name, entry.parent_id, my_dir |
|
216 |
this_name, this_parent, this_dir = entry_data(entry.id, this) |
|
217 |
base_name, base_parent, base_dir = entry_data(entry.id, base) |
|
218 |
other_name, other_parent, other_dir = entry_data(entry.id, other) |
|
1185.31.4
by John Arbash Meinel
Fixing mutter() calls to not have to do string processing. |
219 |
mutter("Dirs: this, base, other %r %r %r", this_dir, base_dir, other_dir) |
220 |
mutter("Names: this, base, other %r %r %r", this_name, base_name, other_name) |
|
974.1.22
by Aaron Bentley
Refactored code |
221 |
old_name = this_name |
222 |
try: |
|
223 |
new_name = threeway_select(this_name, base_name, other_name) |
|
224 |
except ThreeWayConflict: |
|
225 |
new_name = conflict_handler.rename_conflict(entry.id, this_name, |
|
226 |
base_name, other_name) |
|
227 |
||
228 |
old_parent = this_parent |
|
229 |
try: |
|
230 |
new_parent = threeway_select(this_parent, base_parent, other_parent) |
|
231 |
except ThreeWayConflict: |
|
232 |
new_parent = conflict_handler.move_conflict(entry.id, this_dir, |
|
233 |
base_dir, other_dir) |
|
234 |
def get_path(name, parent): |
|
974.1.47
by Aaron Bentley
Merged changes from the merge4 branch |
235 |
if name is not None: |
236 |
if name == "": |
|
237 |
assert parent is None |
|
238 |
return './.' |
|
974.1.22
by Aaron Bentley
Refactored code |
239 |
parent_dir = {this_parent: this_dir, other_parent: other_dir, |
240 |
base_parent: base_dir} |
|
241 |
directory = parent_dir[parent] |
|
1185.31.32
by John Arbash Meinel
Updated the bzr sourcecode to use bzrlib.osutils.pathjoin rather than os.path.join to enforce internal use of / instead of \ |
242 |
return pathjoin(directory, name) |
974.1.22
by Aaron Bentley
Refactored code |
243 |
else: |
974.1.47
by Aaron Bentley
Merged changes from the merge4 branch |
244 |
assert parent is None |
974.1.22
by Aaron Bentley
Refactored code |
245 |
return None |
246 |
||
247 |
old_path = get_path(old_name, old_parent) |
|
248 |
||
909.1.2
by aaron.bentley at utoronto
Fixed rename handling in merge |
249 |
new_entry = changeset.ChangesetEntry(entry.id, old_parent, old_path) |
974.1.22
by Aaron Bentley
Refactored code |
250 |
new_entry.new_path = get_path(new_name, new_parent) |
493
by Martin Pool
- Merge aaron's merge command |
251 |
new_entry.new_parent = new_parent |
909.1.2
by aaron.bentley at utoronto
Fixed rename handling in merge |
252 |
mutter(repr(new_entry)) |
850
by Martin Pool
- Merge merge updates from aaron |
253 |
return new_entry |
254 |
||
255 |
||
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
256 |
def make_merged_contents(entry, this, base, other, conflict_handler, |
974.1.3
by Aaron Bentley
Added merge_factory parameter to merge_flex |
257 |
merge_factory): |
850
by Martin Pool
- Merge merge updates from aaron |
258 |
contents = entry.contents_change |
259 |
if contents is None: |
|
260 |
return None |
|
1185.12.36
by abentley
Removed all remaining use of readonly_path |
261 |
if entry.id in this: |
262 |
this_path = this.id2abspath(entry.id) |
|
263 |
else: |
|
264 |
this_path = None |
|
974.1.3
by Aaron Bentley
Added merge_factory parameter to merge_flex |
265 |
def make_merge(): |
850
by Martin Pool
- Merge merge updates from aaron |
266 |
if this_path is None: |
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
267 |
return conflict_handler.missing_for_merge(entry.id, |
268 |
other.id2path(entry.id)) |
|
974.1.23
by Aaron Bentley
Avoided unnecessary temp files |
269 |
return merge_factory(entry.id, base, other) |
850
by Martin Pool
- Merge merge updates from aaron |
270 |
|
271 |
if isinstance(contents, changeset.ReplaceContents): |
|
1185.12.67
by Aaron Bentley
attempted to clarify the three-way merge code |
272 |
base_contents = contents.old_contents |
273 |
other_contents = contents.new_contents |
|
274 |
if base_contents is None and other_contents is None: |
|
850
by Martin Pool
- Merge merge updates from aaron |
275 |
return None |
1185.12.67
by Aaron Bentley
attempted to clarify the three-way merge code |
276 |
if other_contents is None: |
1185.12.30
by Aaron Bentley
Moved all fooCreate into get_contents |
277 |
this_contents = get_contents(this, entry.id) |
1092.2.24
by Robert Collins
merge from martins newformat branch - brings in transport abstraction |
278 |
if this_path is not None and bzrlib.osutils.lexists(this_path): |
1185.12.67
by Aaron Bentley
attempted to clarify the three-way merge code |
279 |
if this_contents != base_contents: |
1185.10.8
by Aaron Bentley
Conflict handling where OTHER is deleted |
280 |
return conflict_handler.rem_contents_conflict(this_path, |
1185.12.67
by Aaron Bentley
attempted to clarify the three-way merge code |
281 |
this_contents, base_contents) |
850
by Martin Pool
- Merge merge updates from aaron |
282 |
return contents |
283 |
else: |
|
284 |
return None |
|
1185.12.67
by Aaron Bentley
attempted to clarify the three-way merge code |
285 |
elif base_contents is None: |
1092.2.24
by Robert Collins
merge from martins newformat branch - brings in transport abstraction |
286 |
if this_path is None or not bzrlib.osutils.lexists(this_path): |
850
by Martin Pool
- Merge merge updates from aaron |
287 |
return contents |
288 |
else: |
|
1185.12.30
by Aaron Bentley
Moved all fooCreate into get_contents |
289 |
this_contents = get_contents(this, entry.id) |
1185.12.67
by Aaron Bentley
attempted to clarify the three-way merge code |
290 |
if this_contents == other_contents: |
850
by Martin Pool
- Merge merge updates from aaron |
291 |
return None |
292 |
else: |
|
293 |
conflict_handler.new_contents_conflict(this_path, |
|
1185.12.67
by Aaron Bentley
attempted to clarify the three-way merge code |
294 |
other_contents) |
295 |
elif isinstance(base_contents, changeset.TreeFileCreate) and \ |
|
296 |
isinstance(other_contents, changeset.TreeFileCreate): |
|
1448
by Robert Collins
revert symlinks correctly |
297 |
return make_merge() |
850
by Martin Pool
- Merge merge updates from aaron |
298 |
else: |
1185.12.33
by Aaron Bentley
Fixed symlink reverting |
299 |
this_contents = get_contents(this, entry.id) |
1185.12.67
by Aaron Bentley
attempted to clarify the three-way merge code |
300 |
if this_contents == base_contents: |
1185.12.33
by Aaron Bentley
Fixed symlink reverting |
301 |
return contents |
1185.12.67
by Aaron Bentley
attempted to clarify the three-way merge code |
302 |
elif this_contents == other_contents: |
1185.12.33
by Aaron Bentley
Fixed symlink reverting |
303 |
return None |
1185.12.67
by Aaron Bentley
attempted to clarify the three-way merge code |
304 |
elif base_contents == other_contents: |
1185.12.33
by Aaron Bentley
Fixed symlink reverting |
305 |
return None |
306 |
else: |
|
1185.12.68
by Aaron Bentley
tweaked spacing |
307 |
conflict_handler.threeway_contents_conflict(this_path, |
308 |
this_contents, |
|
309 |
base_contents, |
|
310 |
other_contents) |
|
1185.12.33
by Aaron Bentley
Fixed symlink reverting |
311 |
|
850
by Martin Pool
- Merge merge updates from aaron |
312 |
|
313 |
def make_merged_metadata(entry, base, other): |
|
1398
by Robert Collins
integrate in Gustavos x-bit patch |
314 |
metadata = entry.metadata_change |
315 |
if metadata is None: |
|
316 |
return None |
|
1185.12.36
by abentley
Removed all remaining use of readonly_path |
317 |
assert isinstance(metadata, changeset.ChangeExecFlag) |
318 |
if metadata.new_exec_flag is None: |
|
319 |
return None |
|
320 |
elif metadata.old_exec_flag is None: |
|
321 |
return metadata |
|
322 |
else: |
|
323 |
return ExecFlagMerge(base, other, entry.id) |
|
493
by Martin Pool
- Merge aaron's merge command |
324 |
|
325 |
||
1434
by Robert Collins
merge Gustavos executable2 patch |
326 |
class ExecFlagMerge(object): |
1185.12.36
by abentley
Removed all remaining use of readonly_path |
327 |
def __init__(self, base_tree, other_tree, file_id): |
328 |
self.base_tree = base_tree |
|
329 |
self.other_tree = other_tree |
|
330 |
self.file_id = file_id |
|
493
by Martin Pool
- Merge aaron's merge command |
331 |
|
332 |
def apply(self, filename, conflict_handler, reverse=False): |
|
333 |
if not reverse: |
|
1185.12.36
by abentley
Removed all remaining use of readonly_path |
334 |
base = self.base_tree |
335 |
other = self.other_tree |
|
493
by Martin Pool
- Merge aaron's merge command |
336 |
else: |
1185.12.36
by abentley
Removed all remaining use of readonly_path |
337 |
base = self.other_tree |
338 |
other = self.base_tree |
|
339 |
base_exec_flag = base.is_executable(self.file_id) |
|
340 |
other_exec_flag = other.is_executable(self.file_id) |
|
1434
by Robert Collins
merge Gustavos executable2 patch |
341 |
this_mode = os.stat(filename).st_mode |
342 |
this_exec_flag = bool(this_mode & 0111) |
|
343 |
if (base_exec_flag != other_exec_flag and |
|
344 |
this_exec_flag != other_exec_flag): |
|
345 |
assert this_exec_flag == base_exec_flag |
|
346 |
current_mode = os.stat(filename).st_mode |
|
347 |
if other_exec_flag: |
|
348 |
umask = os.umask(0) |
|
349 |
os.umask(umask) |
|
350 |
to_mode = current_mode | (0100 & ~umask) |
|
351 |
# Enable x-bit for others only if they can read it.
|
|
352 |
if current_mode & 0004: |
|
353 |
to_mode |= 0001 & ~umask |
|
354 |
if current_mode & 0040: |
|
355 |
to_mode |= 0010 & ~umask |
|
356 |
else: |
|
357 |
to_mode = current_mode & ~0111 |
|
358 |
os.chmod(filename, to_mode) |
|
359 |