1 |
This includes numerous logic adjustments that are needed to support |
2 |
protected symlinks. The new diff_mixed function is used for diffs |
3 |
between arbitrary file types. For example, a diff between two symlinks |
4 |
looks like this: |
5 |
|
6 |
-SYM: /foo/bar -> baz |
7 |
+SYM: /foo/bar -> blah |
8 |
|
9 |
X-Gentoo-Bug: 485598 |
10 |
X-Gentoo-Bug-URL: https://bugs.gentoo.org/show_bug.cgi?id=485598 |
11 |
--- |
12 |
This updated patch fixes diff_mixed to handle files that don't exist. These |
13 |
are displayed as "/dev/null". Also, I renamed diffstatusoutput_symlink to |
14 |
diffstatusoutput_mixed, since it works with mixed file types. Thanks to |
15 |
Mike Gilbert <floppym@g.o> for reporting the issue with files that |
16 |
don't exist. |
17 |
|
18 |
bin/dispatch-conf | 40 ++++++------ |
19 |
pym/portage/dispatch_conf.py | 142 ++++++++++++++++++++++++++++++++++++------- |
20 |
2 files changed, 144 insertions(+), 38 deletions(-) |
21 |
|
22 |
diff --git a/bin/dispatch-conf b/bin/dispatch-conf |
23 |
index 6d2ae94..80dafd6 100755 |
24 |
--- a/bin/dispatch-conf |
25 |
+++ b/bin/dispatch-conf |
26 |
@@ -15,15 +15,15 @@ from __future__ import print_function |
27 |
|
28 |
from stat import ST_GID, ST_MODE, ST_UID |
29 |
from random import random |
30 |
-import atexit, re, shutil, stat, sys |
31 |
+import atexit, io, re, functools, shutil, sys |
32 |
from os import path as osp |
33 |
if osp.isfile(osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), ".portage_not_installed")): |
34 |
sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym")) |
35 |
import portage |
36 |
portage._internal_caller = True |
37 |
from portage import os |
38 |
-from portage import _unicode_decode |
39 |
-from portage.dispatch_conf import diffstatusoutput |
40 |
+from portage import _encodings, _unicode_decode |
41 |
+from portage.dispatch_conf import diffstatusoutput, diff_mixed |
42 |
from portage.process import find_binary, spawn |
43 |
|
44 |
FIND_EXTANT_CONFIGS = "find '%s' %s -name '._cfg????_%s' ! -name '.*~' ! -iname '.*.bak' -print" |
45 |
@@ -72,6 +72,11 @@ def cmd_var_is_valid(cmd): |
46 |
|
47 |
return find_binary(cmd[0]) is not None |
48 |
|
49 |
+def diff(file1, file2): |
50 |
+ return diff_mixed( |
51 |
+ functools.partial(diffstatusoutput, DIFF_CONTENTS), |
52 |
+ file1, file2) |
53 |
+ |
54 |
class dispatch: |
55 |
options = {} |
56 |
|
57 |
@@ -89,8 +94,6 @@ class dispatch: |
58 |
or not os.path.exists(self.options["log-file"]): |
59 |
open(self.options["log-file"], 'w').close() # Truncate it |
60 |
os.chmod(self.options["log-file"], 0o600) |
61 |
- else: |
62 |
- self.options["log-file"] = "/dev/null" |
63 |
|
64 |
pager = self.options.get("pager") |
65 |
if pager is None or not cmd_var_is_valid(pager): |
66 |
@@ -148,9 +151,6 @@ class dispatch: |
67 |
portage.util.shlex_split( |
68 |
portage.settings.get('CONFIG_PROTECT_MASK', ''))) |
69 |
|
70 |
- def diff(file1, file2): |
71 |
- return diffstatusoutput(DIFF_CONTENTS, file1, file2) |
72 |
- |
73 |
# |
74 |
# Remove new configs identical to current |
75 |
# and |
76 |
@@ -166,7 +166,7 @@ class dispatch: |
77 |
mrgfail = portage.dispatch_conf.rcs_archive(archive, conf['current'], conf['new'], mrgconf) |
78 |
else: |
79 |
mrgfail = portage.dispatch_conf.file_archive(archive, conf['current'], conf['new'], mrgconf) |
80 |
- if os.path.exists(archive + '.dist'): |
81 |
+ if os.path.lexists(archive + '.dist'): |
82 |
unmodified = len(diff(conf['current'], archive + '.dist')[1]) == 0 |
83 |
else: |
84 |
unmodified = 0 |
85 |
@@ -181,7 +181,7 @@ class dispatch: |
86 |
|
87 |
if newconf == mrgconf and \ |
88 |
self.options.get('ignore-previously-merged') != 'yes' and \ |
89 |
- os.path.exists(archive+'.dist') and \ |
90 |
+ os.path.lexists(archive+'.dist') and \ |
91 |
len(diff(archive+'.dist', conf['new'])[1]) == 0: |
92 |
# The current update is identical to the archived .dist |
93 |
# version that has previously been merged. |
94 |
@@ -254,6 +254,11 @@ class dispatch: |
95 |
|
96 |
valid_input = "qhtnmlezu" |
97 |
|
98 |
+ def diff_pager(file1, file2): |
99 |
+ cmd = self.options['diff'] % (file1, file2) |
100 |
+ cmd += pager |
101 |
+ spawn_shell(cmd) |
102 |
+ |
103 |
for conf in confs: |
104 |
count = count + 1 |
105 |
|
106 |
@@ -266,14 +271,10 @@ class dispatch: |
107 |
while 1: |
108 |
clear_screen() |
109 |
if show_new_diff: |
110 |
- cmd = self.options['diff'] % (conf['new'], mrgconf) |
111 |
- cmd += pager |
112 |
- spawn_shell(cmd) |
113 |
+ diff_mixed(diff_pager, conf['new'], mrgconf) |
114 |
show_new_diff = 0 |
115 |
else: |
116 |
- cmd = self.options['diff'] % (conf['current'], newconf) |
117 |
- cmd += pager |
118 |
- spawn_shell(cmd) |
119 |
+ diff_mixed(diff_pager, conf['current'], newconf) |
120 |
|
121 |
print() |
122 |
print('>> (%i of %i) -- %s' % (count, len(confs), conf ['current'])) |
123 |
@@ -357,7 +358,12 @@ class dispatch: |
124 |
def replace (self, newconf, curconf): |
125 |
"""Replace current config with the new/merged version. Also logs |
126 |
the diff of what changed into the configured log file.""" |
127 |
- os.system((DIFF_CONTENTS % (curconf, newconf)) + '>>' + self.options["log-file"]) |
128 |
+ if "log-file" in self.options: |
129 |
+ status, output = diff(curconf, newconf) |
130 |
+ with io.open(self.options["log-file"], mode="a", |
131 |
+ encoding=_encodings["stdio"]) as f: |
132 |
+ f.write(output + "\n") |
133 |
+ |
134 |
try: |
135 |
os.rename(newconf, curconf) |
136 |
except (IOError, os.error) as why: |
137 |
diff --git a/pym/portage/dispatch_conf.py b/pym/portage/dispatch_conf.py |
138 |
index 113d965..d921c26 100644 |
139 |
--- a/pym/portage/dispatch_conf.py |
140 |
+++ b/pym/portage/dispatch_conf.py |
141 |
@@ -6,11 +6,12 @@ |
142 |
# Library by Wayne Davison <gentoo@×××××.net>, derived from code |
143 |
# written by Jeremy Wohl (http://igmus.org) |
144 |
|
145 |
-from __future__ import print_function |
146 |
+from __future__ import print_function, unicode_literals |
147 |
|
148 |
-import os, shutil, subprocess, sys |
149 |
+import functools, io, os, shutil, stat, subprocess, sys, tempfile |
150 |
|
151 |
import portage |
152 |
+from portage import _encodings |
153 |
from portage.env.loaders import KeyValuePairFileLoader |
154 |
from portage.localization import _ |
155 |
from portage.util import shlex_split, varexpand |
156 |
@@ -50,6 +51,58 @@ def diffstatusoutput(cmd, file1, file2): |
157 |
output = output[:-1] |
158 |
return (proc.wait(), output) |
159 |
|
160 |
+def diff_mixed(func, file1, file2): |
161 |
+ tempdir = None |
162 |
+ try: |
163 |
+ if os.path.islink(file1) and \ |
164 |
+ not os.path.islink(file2) and \ |
165 |
+ os.path.isfile(file1) and \ |
166 |
+ os.path.isfile(file2): |
167 |
+ # If a regular file replaces a symlink to a regular |
168 |
+ # file, then show the diff between the regular files |
169 |
+ # (bug #330221). |
170 |
+ diff_files = (file2, file2) |
171 |
+ else: |
172 |
+ files = [file1, file2] |
173 |
+ diff_files = [file1, file2] |
174 |
+ for i in range(len(diff_files)): |
175 |
+ try: |
176 |
+ st = os.lstat(diff_files[i]) |
177 |
+ except OSError: |
178 |
+ st = None |
179 |
+ if st is not None and stat.S_ISREG(st.st_mode): |
180 |
+ continue |
181 |
+ |
182 |
+ if tempdir is None: |
183 |
+ tempdir = tempfile.mkdtemp() |
184 |
+ diff_files[i] = os.path.join(tempdir, "%d" % i) |
185 |
+ if st is None: |
186 |
+ content = "/dev/null\n" |
187 |
+ elif stat.S_ISLNK(st.st_mode): |
188 |
+ link_dest = os.readlink(files[i]) |
189 |
+ content = "SYM: %s -> %s\n" % \ |
190 |
+ (file1, link_dest) |
191 |
+ elif stat.S_ISDIR(st.st_mode): |
192 |
+ content = "DIR: %s\n" % (file1,) |
193 |
+ elif stat.S_ISFIFO(st.st_mode): |
194 |
+ content = "FIF: %s\n" % (file1,) |
195 |
+ else: |
196 |
+ content = "DEV: %s\n" % (file1,) |
197 |
+ with io.open(diff_files[i], mode='w', |
198 |
+ encoding=_encodings['stdio']) as f: |
199 |
+ f.write(content) |
200 |
+ |
201 |
+ return func(diff_files[0], diff_files[1]) |
202 |
+ |
203 |
+ finally: |
204 |
+ if tempdir is not None: |
205 |
+ shutil.rmtree(tempdir) |
206 |
+ |
207 |
+def diffstatusoutput_mixed(cmd, file1, file2): |
208 |
+ return diff_mixed( |
209 |
+ functools.partial(diffstatusoutput, cmd), |
210 |
+ file1, file2) |
211 |
+ |
212 |
def read_config(mandatory_opts): |
213 |
eprefix = portage.settings["EPREFIX"] |
214 |
if portage._not_installed: |
215 |
@@ -103,35 +156,57 @@ def rcs_archive(archive, curconf, newconf, mrgconf): |
216 |
except OSError: |
217 |
pass |
218 |
|
219 |
- if os.path.isfile(curconf): |
220 |
+ try: |
221 |
+ curconf_st = os.lstat(curconf) |
222 |
+ except OSError: |
223 |
+ curconf_st = None |
224 |
+ |
225 |
+ if curconf_st is not None and \ |
226 |
+ (stat.S_ISREG(curconf_st.st_mode) or |
227 |
+ stat.S_ISLNK(curconf_st.st_mode)): |
228 |
try: |
229 |
- shutil.copy2(curconf, archive) |
230 |
+ if stat.S_ISLNK(curconf_st.st_mode): |
231 |
+ os.symlink(os.readlink(curconf), archive) |
232 |
+ else: |
233 |
+ shutil.copy2(curconf, archive) |
234 |
except(IOError, os.error) as why: |
235 |
print(_('dispatch-conf: Error copying %(curconf)s to %(archive)s: %(reason)s; fatal') % \ |
236 |
{"curconf": curconf, "archive": archive, "reason": str(why)}, file=sys.stderr) |
237 |
|
238 |
- if os.path.exists(archive + ',v'): |
239 |
+ if os.path.lexists(archive + ',v'): |
240 |
os.system(RCS_LOCK + ' ' + archive) |
241 |
os.system(RCS_PUT + ' ' + archive) |
242 |
|
243 |
ret = 0 |
244 |
- if newconf != '': |
245 |
+ mystat = None |
246 |
+ if newconf: |
247 |
+ try: |
248 |
+ mystat = os.lstat(newconf) |
249 |
+ except OSError: |
250 |
+ pass |
251 |
+ |
252 |
+ if mystat is not None and \ |
253 |
+ (stat.S_ISREG(mystat.st_mode) or |
254 |
+ stat.S_ISLNK(mystat.st_mode)): |
255 |
os.system(RCS_GET + ' -r' + RCS_BRANCH + ' ' + archive) |
256 |
- has_branch = os.path.exists(archive) |
257 |
+ has_branch = os.path.lexists(archive) |
258 |
if has_branch: |
259 |
os.rename(archive, archive + '.dist') |
260 |
|
261 |
try: |
262 |
- shutil.copy2(newconf, archive) |
263 |
+ if stat.S_ISLNK(mystat.st_mode): |
264 |
+ os.symlink(os.readlink(newconf), archive) |
265 |
+ else: |
266 |
+ shutil.copy2(newconf, archive) |
267 |
except(IOError, os.error) as why: |
268 |
print(_('dispatch-conf: Error copying %(newconf)s to %(archive)s: %(reason)s; fatal') % \ |
269 |
{"newconf": newconf, "archive": archive, "reason": str(why)}, file=sys.stderr) |
270 |
|
271 |
if has_branch: |
272 |
- if mrgconf != '': |
273 |
+ if mrgconf and os.path.isfile(archive) and \ |
274 |
+ os.path.isfile(mrgconf): |
275 |
# This puts the results of the merge into mrgconf. |
276 |
ret = os.system(RCS_MERGE % (archive, mrgconf)) |
277 |
- mystat = os.lstat(newconf) |
278 |
os.chmod(mrgconf, mystat.st_mode) |
279 |
os.chown(mrgconf, mystat.st_uid, mystat.st_gid) |
280 |
os.rename(archive, archive + '.dist.new') |
281 |
@@ -153,10 +228,11 @@ def file_archive(archive, curconf, newconf, mrgconf): |
282 |
pass |
283 |
|
284 |
# Archive the current config file if it isn't already saved |
285 |
- if (os.path.exists(archive) and |
286 |
- len(diffstatusoutput("diff -aq '%s' '%s'", curconf, archive)[1]) != 0): |
287 |
+ if (os.path.lexists(archive) and |
288 |
+ len(diffstatusoutput_mixed( |
289 |
+ "diff -aq '%s' '%s'", curconf, archive)[1]) != 0): |
290 |
suf = 1 |
291 |
- while suf < 9 and os.path.exists(archive + '.' + str(suf)): |
292 |
+ while suf < 9 and os.path.lexists(archive + '.' + str(suf)): |
293 |
suf += 1 |
294 |
|
295 |
while suf > 1: |
296 |
@@ -165,26 +241,50 @@ def file_archive(archive, curconf, newconf, mrgconf): |
297 |
|
298 |
os.rename(archive, archive + '.1') |
299 |
|
300 |
- if os.path.isfile(curconf): |
301 |
+ try: |
302 |
+ curconf_st = os.lstat(curconf) |
303 |
+ except OSError: |
304 |
+ curconf_st = None |
305 |
+ |
306 |
+ if curconf_st is not None and \ |
307 |
+ (stat.S_ISREG(curconf_st.st_mode) or |
308 |
+ stat.S_ISLNK(curconf_st.st_mode)): |
309 |
try: |
310 |
- shutil.copy2(curconf, archive) |
311 |
+ if stat.S_ISLNK(curconf_st.st_mode): |
312 |
+ os.symlink(os.readlink(curconf), archive) |
313 |
+ else: |
314 |
+ shutil.copy2(curconf, archive) |
315 |
except(IOError, os.error) as why: |
316 |
print(_('dispatch-conf: Error copying %(curconf)s to %(archive)s: %(reason)s; fatal') % \ |
317 |
{"curconf": curconf, "archive": archive, "reason": str(why)}, file=sys.stderr) |
318 |
|
319 |
- if newconf != '': |
320 |
+ mystat = None |
321 |
+ if newconf: |
322 |
+ try: |
323 |
+ mystat = os.lstat(newconf) |
324 |
+ except OSError: |
325 |
+ pass |
326 |
+ |
327 |
+ if mystat is not None and \ |
328 |
+ (stat.S_ISREG(mystat.st_mode) or |
329 |
+ stat.S_ISLNK(mystat.st_mode)): |
330 |
# Save off new config file in the archive dir with .dist.new suffix |
331 |
+ newconf_archive = archive + '.dist.new' |
332 |
try: |
333 |
- shutil.copy2(newconf, archive + '.dist.new') |
334 |
+ if stat.S_ISLNK(mystat.st_mode): |
335 |
+ os.symlink(os.readlink(newconf), newconf_archive) |
336 |
+ else: |
337 |
+ shutil.copy2(newconf, newconf_archive) |
338 |
except(IOError, os.error) as why: |
339 |
print(_('dispatch-conf: Error copying %(newconf)s to %(archive)s: %(reason)s; fatal') % \ |
340 |
{"newconf": newconf, "archive": archive + '.dist.new', "reason": str(why)}, file=sys.stderr) |
341 |
|
342 |
ret = 0 |
343 |
- if mrgconf != '' and os.path.exists(archive + '.dist'): |
344 |
+ if mrgconf and os.path.isfile(curconf) and \ |
345 |
+ os.path.isfile(newconf) and \ |
346 |
+ os.path.isfile(archive + '.dist'): |
347 |
# This puts the results of the merge into mrgconf. |
348 |
ret = os.system(DIFF3_MERGE % (curconf, archive + '.dist', newconf, mrgconf)) |
349 |
- mystat = os.lstat(newconf) |
350 |
os.chmod(mrgconf, mystat.st_mode) |
351 |
os.chown(mrgconf, mystat.st_uid, mystat.st_gid) |
352 |
|
353 |
@@ -195,7 +295,7 @@ def rcs_archive_post_process(archive): |
354 |
"""Check in the archive file with the .dist.new suffix on the branch |
355 |
and remove the one with the .dist suffix.""" |
356 |
os.rename(archive + '.dist.new', archive) |
357 |
- if os.path.exists(archive + '.dist'): |
358 |
+ if os.path.lexists(archive + '.dist'): |
359 |
# Commit the last-distributed version onto the branch. |
360 |
os.system(RCS_LOCK + RCS_BRANCH + ' ' + archive) |
361 |
os.system(RCS_PUT + ' -r' + RCS_BRANCH + ' ' + archive) |
362 |
@@ -207,5 +307,5 @@ def rcs_archive_post_process(archive): |
363 |
|
364 |
def file_archive_post_process(archive): |
365 |
"""Rename the archive file with the .dist.new suffix to a .dist suffix""" |
366 |
- if os.path.exists(archive + '.dist.new'): |
367 |
+ if os.path.lexists(archive + '.dist.new'): |
368 |
os.rename(archive + '.dist.new', archive + '.dist') |
369 |
-- |
370 |
2.0.4 |