Add some useful informations when using $EDITOR.
[gentoolkit.git] / trunk / src / epkgmove / epkgmove
1 #!/usr/bin/python -O
2 # Copyright 2004 Ian Leitch
3 # Copyright 1999-2004 Gentoo Foundation
4 # $Header$
5 #
6 # Author:
7 #  Ian Leitch <port001@gentoo.org>
8 #
9
10 import os
11 import re
12 import sys
13 import signal
14 import commands
15 from time import sleep
16 from random import randint
17 from optparse import OptionParser
18
19 import portage
20 try:
21         from portage.output import *
22 except ImportError:
23         from output import *
24
25 __author__      =       "Ian Leitch"
26 __email__       =       "port001@gentoo.org"
27 __productname__ =       "epkgmove"
28 __version__     =       "1.3.1 - \"Moving Fusion + Bandages + Plasters\""
29 __description__ =       "A tool for moving and renaming packages in CVS"
30
31 def print_usage():
32
33     print
34     print "%s %s [ %s ] [ %s ] [ %s ]" % (white("Usage:"), turquoise(__productname__), green("option"), green("origin"), green("destination"))
35     print "   '%s' and '%s' are expected as a full package name, e.g. net-im/gaim" % (green("origin"), green("destination"))
36     print "   See %s --help for a list of options" % __productname__
37     print
38
39 def check_cwd(portdir):
40
41     if os.getcwd() != portdir:
42         print
43         print "%s Not in PORTDIR!" % yellow(" *")
44         print "%s Setting to: %s" % (yellow("   *"), os.getcwd())
45         os.environ["PORTDIR"]=os.getcwd()
46         return os.getcwd()
47         
48     return portdir
49
50 def check_args(args, portdir, options, cvscmd):
51
52     booboo = False
53     re_expr = "^[\da-zA-Z-]{,}/[\d\w0-9-]{,}$"
54     o_re_expr = re.compile(re_expr)
55
56     if len(args) == 0:
57         print "\n%s ERROR: errr, didn't you forget something" % red("!!!"),
58         count = range(3)
59         for second in count:
60             sys.stdout.write(".")
61             sys.stdout.flush()
62             sleep(1)
63         sys.stdout.write("?")
64         sys.stdout.flush()
65         sleep(1)
66         print "\n\n%s %s hits you with a clue stick %s" % (green("    *"), __productname__, green("*"))
67         sleep(1)
68         print_usage()
69         sys.exit(1)
70
71     if options.remove:
72         if len(args) > 1:
73             error("Please remove packages one at a time")
74             sys.exit(1)
75         elif not o_re_expr.match(args[0].rstrip("/")):
76             error("Expected full package name as argument")
77             sys.exit(1)
78         elif not os.path.exists(os.path.join(portdir, args[0].rstrip("/"))):
79             error("No such package '%s'" % args[0].rstrip("/"))
80             sys.exit(1)
81         else:
82             if not options.cvs_up:
83                 update_categories(portdir, args, cvscmd["update"])
84             return (args[0].rstrip("/"), None)
85
86     if len(args) == 2:
87         if not o_re_expr.match(args[0].rstrip("/")):
88             error("Expected full package name as origin argument")
89             booboo = True
90         elif not o_re_expr.match(args[1].rstrip("/")):
91             error("Expected full package name as destination argument")
92             booboo = True
93
94         if booboo == True:
95             sys.exit(1)
96     else:
97         error("Expected two arguments as input.")
98         print_usage()
99         sys.exit(1)
100
101     if not options.cvs_up:
102         update_categories(portdir, args, cvscmd["update"])
103
104     if not os.path.exists(os.path.join(portdir, args[0].rstrip("/"))):
105         error("No such package '%s'" % args[0].rstrip("/"))
106         booboo = True
107     elif os.path.exists(os.path.join(portdir, args[1].rstrip("/"))):
108         error("Package '%s' already exists" % args[1].rstrip("/"))
109         booboo = True
110
111     if booboo == True:
112         sys.exit(1)
113     
114     return (args[0].rstrip("/"), args[1].rstrip("/"))
115
116 def check_repos(portdir):
117
118     files = os.listdir(portdir)
119
120     for file in files:
121         if not os.path.isdir(file):
122             files.remove(file)
123                                                            
124     if "CVS" not in files:
125         error("Current directory doesn't look like a CVS repository")
126         sys.exit(1)
127
128 def check_commit_queue():
129
130     empty = True
131
132     print
133     print "%s Checking commit queue for outstanding changes..." % green(" *")
134     print "%s Note: This may take a VERY long time" % yellow("   *")
135     print
136         
137     output = commands.getoutput("cvs diff")
138     
139     for line in output.split("\n"):
140         if not line.startswith("?"):
141             empty = False
142             break
143             
144     if empty == False:
145         error("Commit queue not empty! Please commit all outstanding changes before using %s." % __productname__)
146         sys.exit(1)
147
148 def update_categories(portdir, catpkgs, cvsupcmd):
149
150     my_catpkgs = []
151
152     print
153     print "%s Updating categories: " % green(" *")
154
155     if len(catpkgs) >= 2:
156         if catpkgs[0].split("/", 1)[0] == catpkgs[1].split("/", 1)[0]:
157             my_catpkgs.append(catpkgs[0])
158         else:
159             my_catpkgs.append(catpkgs[0])
160             my_catpkgs.append(catpkgs[1])
161     else:
162         my_catpkgs.append(catpkgs[0])
163
164     for catpkg in my_catpkgs:
165         (category, package) = catpkg.split("/", 1)
166         if os.path.exists(os.path.join(portdir, category)):
167             os.chdir(os.path.join(portdir, category))
168             print "   %s %s" % (green("*"), category)
169             do_cmd(cvsupcmd)
170         else:
171             print "   %s %s" % (red("!"), category)
172
173     os.chdir(portdir)
174     
175 def error(msg):
176
177     sys.stderr.write("\n%s ERROR: %s\n" % (red("!!!"), msg))
178
179 def signal_handler(signal_number=None, stack_frame=None):
180
181     error("Caught SIGINT; exiting...")
182     sys.exit(1)
183     os.kill(0, signal.SIGKILL)
184
185 def do_cmd(cmd):
186
187     (status, output) = commands.getstatusoutput(cmd)
188     if status != 0:
189         error("Command '%s' failed with exit status %d." % (cmd, status))
190         for line in output.split("\n"):
191             if line != "":
192                 print "    %s %s" % (red("!"), line)
193         sys.exit(1)
194        
195 class CVSAbstraction:
196
197     def __init__(self, portdir, oldcatpkg, newcatpkg, cvscmd, options):
198     
199         self._portdir = portdir
200         self._cvscmd = cvscmd
201         self._options = options
202         self._ignore = ("CVS")
203
204         self._old_category = ""
205         self._old_package = ""
206         self._new_category = ""
207         self._new_package = ""
208         self._old_catpkg = oldcatpkg
209         self._new_catpkg = newcatpkg
210
211         self._action = ""
212         
213         self._distinguish_action(oldcatpkg, newcatpkg)
214
215     def _distinguish_action(self, oldcatpkg, newcatpkg):
216                 
217         (self._old_category, self._old_package) = oldcatpkg.split("/")
218         if newcatpkg:
219             (self._new_category, self._new_package) = newcatpkg.split("/")
220
221         if self._old_category != self._new_category and self._new_category:
222             if self._old_package != self._new_package and self._new_package:
223                 self._action = "MOVE & RENAME"
224             else:
225                 self._action = "MOVE"
226         elif self._old_package != self._new_package and self._new_package:
227             self._action = "RENAME"
228         elif not self._new_package:
229             self._action = "REMOVE"
230         else:
231             error("Unable to distingush required action.")
232             sys.exit(1)
233
234     def __backup(self):
235
236         print "%s Backing up %s..." % (green("   *"), turquoise(self._old_catpkg))
237         
238         if not os.path.exists("/tmp/%s" % __productname__):
239             os.mkdir("/tmp/%s" % __productname__)
240         
241         if os.path.exists("/tmp/%s/%s" % (__productname__, self._old_package)):
242             do_cmd("rm -rf /tmp/%s/%s" % (__productname__, self._old_package))
243             
244         do_cmd("cp -R %s /tmp/%s/%s" % (os.path.join(self._portdir, self._old_catpkg), __productname__, self._old_package))
245
246     def perform_action(self):
247
248         count_down = 5
249
250         print
251         if self._action == "REMOVE":
252             print "%s Performing a '%s' of %s..." % (green(" *"), green(self._action), turquoise(self._old_catpkg))
253         else:
254             print "%s Performing a '%s' of %s to %s..." % (green(" *"), green(self._action), turquoise(self._old_catpkg), yellow(self._new_catpkg))
255         
256         if not self._options.countdown:
257             print "%s Performing in: " % green(" *"),
258             count = range(count_down)
259             count.reverse()
260             for second in count:
261                 sys.stdout.write("%s " % red(str(second + 1)))
262                 sys.stdout.flush()
263                 sleep(1)
264             print
265
266         if not self._action == "REMOVE":
267             self.__backup()
268
269         if self._action == "MOVE & RENAME":
270             self._perform_move_rename()
271         elif self._action == "MOVE":
272             self._perform_move()
273         elif self._action == "RENAME":
274             self._perform_rename()
275         elif self._action == "REMOVE":
276             self._perform_remove()
277
278     def _perform_remove(self):
279
280         deps = self.__get_reverse_deps()
281
282         if deps:
283             print "%s The following ebuild(s) depend on this package:" % red("     *")
284             for dep in deps:
285                 print "%s %s" % (red("       !"), dep)
286             if self._options.force:
287                 print "%s Are you sure you wish to force removal of this package?" % yellow("   *"),
288                 try:
289                     choice = raw_input("(Yes/No): ")
290                 except KeyboardInterrupt:
291                     error("Interrupted by user.")
292                     sys.exit(1)
293                 if choice.strip().lower() != "yes":
294                     error("Bailing on forced removal.")
295                     sys.exit(1)
296             else:
297                 error("Refusing to remove from CVS, package has dependents.")
298                 sys.exit(1)
299
300         self.__remove_old_package()
301
302     def _perform_move(self):
303
304         self.__add_new_package()
305         self.__regen_manifest()
306         self.__update_dependents(self.__get_reverse_deps())
307         self.__remove_old_package()
308         
309     def _perform_move_rename(self):
310
311         self._perform_rename()
312  
313     def _perform_rename(self):
314         
315         self.__rename_files()
316         self.__add_new_package()
317         self.__regen_digests()
318         self.__update_dependents(self.__get_reverse_deps())
319         self.__remove_old_package()
320
321     def __rename_files(self):
322
323         def rename_files(arg, dir, files):
324         
325             if os.path.basename(dir) not in self._ignore:
326                 if os.path.basename(dir) != self._old_package:
327                     for file in files:
328                         new_file = ""
329                         if file.find(self._old_package) >= 0:
330                             new_file = file.replace(self._old_package, self._new_package)
331                             do_cmd("mv %s %s" % (os.path.join(dir, file), os.path.join(dir, new_file)))
332                         if not os.path.isdir(os.path.join(dir, new_file)) and not file.startswith("digest-"):
333                             self.__rename_file_contents(os.path.join(dir, new_file))
334                 else:
335                     for file in files:
336                         if file.endswith(".ebuild"):
337                             new_file = file.replace(self._old_package, self._new_package)
338                             do_cmd("mv %s %s" % (os.path.join(dir, file), os.path.join(dir, new_file)))
339                             self.__rename_file_contents(os.path.join(dir, new_file))
340                         elif file.endswith(".xml"):
341                             self.__rename_file_contents(os.path.join(dir, file))
342
343         print "%s Renaming files..." % green("   *")
344         os.path.walk("/tmp/%s/%s" % (__productname__, self._old_package), rename_files , None)
345         do_cmd("mv /tmp/%s/%s /tmp/%s/%s" % (__productname__, self._old_package, __productname__, self._new_package))
346
347     def __regen_manifest(self, path=None, dep=False):
348
349         if dep:
350             print "%s Regenerating Manifest..." % green("      *")
351         else:
352             print "%s Regenerating Manifest..." % green("   *")
353         if path:
354             os.chdir(path)
355         else:
356             os.chdir(os.path.join(self._portdir, self._new_catpkg))
357         for ebuild in os.listdir("."):
358             if ebuild.endswith(".ebuild"):
359                 do_cmd("/usr/lib/portage/bin/ebuild %s manifest" % ebuild)
360                 break
361
362         self.__gpg_sign(dep)
363
364     def __regen_digests(self):
365
366         print "%s Regenerating digests:" % green("   *")
367         os.chdir(os.path.join(self._portdir, self._new_catpkg))
368         for digest in os.listdir("files/"):
369             if digest.startswith("digest-"):
370                 os.unlink("files/%s" % digest)
371         for ebuild in os.listdir("."):
372             if ebuild.endswith(".ebuild"):
373                 print "      >>> %s" % ebuild   
374                 do_cmd("/usr/lib/portage/bin/ebuild %s digest" % ebuild)
375
376         self.__gpg_sign()
377
378     def __gpg_sign(self, dep=False):
379
380         gpg_cmd = ""
381
382         os.chdir(os.path.join(self._portdir, self._new_catpkg))
383
384         if "sign" in portage.features:
385             if dep:
386                 print "%s GPG Signing Manifest..." % green("      *")
387             else:
388                 print "%s GPG Signing Manifest..." % green("   *")
389             gpg_cmd = "gpg --quiet --sign --clearsign --yes --default-key %s" % portage.settings["PORTAGE_GPG_KEY"]
390             if portage.settings.has_key("PORTAGE_GPG_DIR"):
391                 gpg_cmd = "%s --homedir %s" % (gpg_cmd, portage.settings["PORTAGE_GPG_DIR"])
392             do_cmd("%s Manifest" % gpg_cmd)
393             do_cmd("mv Manifest.asc Manifest")
394         do_cmd("%s 'Manifest recommit'" % self._cvscmd["commit"])
395
396     def __rename_file_contents(self, file):
397         
398         def choice_loop(line, match_count, choice_list, type="name", replace=""):
399
400             new_line = line
401             accepted = False
402             skipp = False
403
404             while(not accepted):
405                 print "         ",
406                 if len(choice_list) > 0:
407                     for choice in choice_list:
408                         print "%s: Replace with '%s'," % (white(choice), green(choice_list[choice])),
409                 if match_count == 0:
410                     print "%s: Pass, %s: Skip this file, %s: Custom" % (white(str(len(choice_list)+1)), white(str(len(choice_list)+2)), white(str(len(choice_list)+3))),
411                 else:
412                     print "%s: Pass, %s: Custom" % (white(str(len(choice_list)+1)), white(str(len(choice_list)+2))),
413                 try:
414                     input = raw_input("%s " % white(":"))
415                 except KeyboardInterrupt:
416                     print
417                     error("Interrupted by user.")
418                     sys.exit(1)
419                 if choice_list.has_key(input):
420                     if type == "name":
421                         new_line = new_line.replace(self._old_package, choice_list[input])
422                         accepted = True
423                     elif type == "P":
424                         new_line = new_line.replace("${P}", choice_list[input])
425                         accepted = True
426                     elif type == "PN":
427                         new_line = new_line.replace("${PN}", choice_list[input])
428                         accepted = True
429                     elif type == "PV":
430                         new_line = new_line.replace("${PV}", choice_list[input])
431                         accepted = True
432                     elif type == "PVR":
433                         new_line = new_line.replace("${PVR}", choice_list[input])
434                         accepted = True
435                     elif type == "PR":
436                         new_line = new_line.replace("${PR}", choice_list[input])
437                         accepted = True
438                     elif type == "PF":
439                         new_line = new_line.replace("${PF}", choice_list[input])
440                         accepted = True
441                 elif input == str(len(choice_list)+1):
442                     accepted = True
443                 elif input == str(len(choice_list)+2) and match_count == 0:
444                     accepted = True
445                     skipp = True
446                 elif input == str(len(choice_list)+3) and match_count == 0 or input == str(len(choice_list)+2) and match_count != 0:
447                     
448                     input_accepted = False
449                     
450                     while(not input_accepted):
451                         try:
452                             custom_input = raw_input("            %s Replacement string: " % green("*"))
453                         except KeyboardInterrupt:
454                             print
455                             error("Interrupted by user.")
456                             sys.exit(1)
457                         while(1):
458                             print "              %s Replace '%s' with '%s'? (Y/N)" % (yellow("*"), white(replace), white(custom_input)),
459                             try:
460                                 yes_no = raw_input(": ")
461                             except KeyboardInterrupt:
462                                 print
463                                 error("Interrupted by user.")
464                                 sys.exit(1)
465                             if yes_no.lower() == "y":
466                                 input_accepted = True
467                                 break
468                             elif yes_no.lower() == "n":
469                                 break
470
471                     new_line = new_line.replace(replace, custom_input)
472                     accepted = True
473                 else:
474                     accepted = False
475                     
476                 if skipp:
477                     break
478
479             return (new_line, skipp)
480
481         found = False
482         contents = []
483         new_contents = []
484         match_count = 0
485         skipp = False
486
487         try:
488             readfd = open(file, "r")
489             contents = readfd.readlines()
490             readfd.close()
491         except IOError, e:
492             error(e)
493             sys.exit(1)
494         
495         for line in contents:
496             if line.find(self._old_package) >= 0:
497                 found = True
498                 break
499
500         if found == True:
501             print "%s Editing %s:" % (green("     *"), white(file.split("/tmp/%s/%s/" % (__productname__, self._old_package))[1]))
502             try:
503                 writefd = open(file, "w")
504             except IOError, e:
505                 error(e)
506                 sys.exit(1)
507             for line in contents:
508                 tmp_line = line 
509                 if not line.startswith("# $Header:"):
510                     if line.find(self._old_package) >= 0:
511                         print "%s %s" % (green("       !"), line.strip().replace(self._old_package, yellow(self._old_package)))
512                         (tmp_line, skipp) = choice_loop(tmp_line, match_count, {"1": self._new_package,
513                                                                                 "2": "${PN}"}, type="name", replace=self._old_package)
514                         match_count += 1
515                         if skipp:
516                             break
517                     if line.find("${P}") >= 0:
518                         print "%s %s" % (green("       !"), line.strip().replace("${P}", yellow("${P}")))
519                         (pkg, version, revising) = portage.pkgsplit(os.path.split(file)[1][:-7])
520                         (tmp_line, skipp) = choice_loop(tmp_line, match_count, {"1": "%s-%s" % (pkg, version),
521                                                                                 "2": "%s-%s" % (self._old_package, version)}, type="P", replace="${P}")
522                         match_count += 1
523                         if skipp:
524                             break
525                     if line.find("${PN}") >= 0:
526                         print "%s %s" % (green("       !"), line.strip().replace("${PN}", yellow("${PN}")))
527                         (tmp_line, skipp) = choice_loop(tmp_line, match_count, {"1": self._new_package,
528                                                                                 "2": self._old_package}, type="PN", replace="${PN}")
529                         match_count += 1
530                         if skipp:
531                             break
532                     if line.find("${PV}") >= 0:
533                         print "%s %s" % (green("       !"), line.strip().replace("${PV}", yellow("${PV}")))
534                         (pkg, version, revision) = portage.pkgsplit(os.path.split(file)[1][:-7])
535                         (tmp_line, skipp) = choice_loop(tmp_line, match_count, {"1": version}, type="PV", replace="${PV}")
536                         match_count += 1
537                         if skipp:
538                             break
539                     if line.find("${PVR}") >= 0:
540                         print "%s %s" % (green("       !"), line.strip().replace("${PVR}", yellow("${PVR}")))
541                         (pkg, version, revision) = portage.pkgsplit(os.path.split(file)[1][:-7])
542                         (tmp_line, skipp) = choice_loop(tmp_line, match_count, {"1": "%s-%s" % (version, revision)}, type="PVR", replace="${PVR}")
543                         match_count += 1
544                         if skipp:
545                             break
546                     if line.find("${PR}") >= 0:
547                         print "%s %s" % (green("       !"), line.strip().replace("${PR}", yellow("${PR}")))
548                         (pkg, version, revision) = portage.pkgsplit(os.path.split(file)[1][:-7])
549                         (tmp_line, skipp) = choice_loop(tmp_line, match_count, {"1": revision}, type="PR", replace="${PR}")
550                         match_count += 1
551                         if skipp:
552                             break
553                     if line.find("${PF}") >= 0:
554                         print "%s %s" % (green("       !"), line.strip().replace("${PF}", yellow("${PF}")))
555                         (pkg, version, revision) = portage.pkgsplit(os.path.split(file)[1][:-7])
556                         (tmp_line, skipp) = choice_loop(tmp_line, match_count, {"1": "%s-%s-%s" % (pkg, version, revision),
557                                                                                 "2": "%s-%s-%s" % (self._old_package, version, revision)}, type="PF", replace="${PF}")
558                         match_count += 1
559                         if skipp:
560                             break
561                     if line.find("${P/") >= 0:
562                         start = line.find("${P/")
563                         i = 0
564                         hi_str = ""
565                         while(line[start + (i - 1)] != "}"):
566                             hi_str += line[start + i]
567                             i += 1
568                         print "%s %s" % (green("       !"), line.strip().replace(hi_str, red(hi_str)))
569                         (tmp_line, skipp) = choice_loop(tmp_line, match_count, {}, type=None, replace=hi_str)
570                         match_count += 1
571                         if skipp:
572                             break
573                     if line.find("${PN/") >= 0:
574                         start = line.find("${PN/")
575                         i = 0
576                         hi_str = ""
577                         while(line[start + (i - 1)] != "}"):
578                             hi_str += line[start + i]
579                             i += 1
580                         print "%s %s" % (green("       !"), line.strip().replace(hi_str, red(hi_str)))
581                         (tmp_line, skipp) = choice_loop(tmp_line, match_count, {}, type=None, replace=hi_str)
582                         match_count += 1
583                         if skipp:
584                             break
585                     if line.find("${PV/") >= 0:
586                         start = line.find("${PV/")
587                         i = 0
588                         hi_str = ""
589                         while(line[start + (i - 1)] != "}"):
590                             hi_str += line[start + i]
591                             i += 1
592                         print "%s %s" % (green("       !"), line.strip().replace(hi_str, red(hi_str)))
593                         (tmp_line, skipp) = choice_loop(tmp_line, match_count, {}, type=None, replace=hi_str)
594                         match_count += 1
595                         if skipp:
596                             break
597                     if line.find("${PVR/") >= 0:
598                         start = line.find("${PVR/")
599                         i = 0
600                         hi_str = ""
601                         while(line[start + (i - 1)] != "}"):
602                             hi_str += line[start + i]
603                             i += 1
604                         print "%s %s" % (green("       !"), line.strip().replace(hi_str, red(hi_str)))
605                         (tmp_line, skipp) = choice_loop(tmp_line, match_count, {}, type=None, replace=hi_str)
606                         match_count += 1
607                         if skipp:
608                             break
609                     if line.find("${PR/") >= 0:
610                         start = line.find("${PR/")
611                         i = 0
612                         hi_str = ""
613                         while(line[start + (i - 1)] != "}"):
614                             hi_str += line[start + i]
615                             i += 1
616                         print "%s %s" % (green("       !"), line.strip().replace(hi_str, red(hi_str)))
617                         (tmp_line, skipp) = choice_loop(tmp_line, match_count, {}, type=None, replace=hi_str)
618                         match_count += 1
619                         if skipp:
620                             break
621                     if line.find("${PF/") >= 0:
622                         start = line.find("${PF/")
623                         i = 0
624                         hi_str = ""
625                         while(line[start + (i - 1)] != "}"):
626                             hi_str += line[start + i]
627                             i += 1
628                         print "%s %s" % (green("       !"), line.strip().replace(hi_str, red(hi_str)))
629                         (tmp_line, skipp) = choice_loop(tmp_line, match_count, {}, type=None, replace=hi_str)
630                         match_count += 1
631                         if skipp:
632                             break
633
634                 new_contents.append(tmp_line)
635
636             if not skipp:
637                 for line in new_contents:
638                     writefd.write(line)
639             else:
640                 for line in contents:
641                     writefd.write(line)
642             
643             writefd.close()
644
645     def __update_dependents(self, dep_list):
646
647         if len(dep_list) <= 0:
648             return
649         
650         print "%s Updating dependents:" % green("   *")
651         os.chdir(self._portdir)
652
653         for dep in dep_list:
654             print "      >>> %s" % dep
655             new_contents = []
656             (category, pkg) = dep.split("/")
657             pkg_split = portage.pkgsplit(pkg)
658             os.chdir(self._portdir)
659             do_cmd("%s %s" % (self._cvscmd["update"], os.path.join(category, pkg_split[0])))
660
661             try:
662                 readfd = open(os.path.join(self._portdir, category, pkg_split[0], "%s.ebuild" % pkg), "r")
663                 contents = readfd.readlines()
664                 readfd.close()
665             except IOError, e:
666                 error(e)
667                 sys.exit(1)
668             
669             for line in contents:
670                 if self._old_catpkg in line:
671                     new_contents.append(line.replace(self._old_catpkg, self._new_catpkg))
672                 else:
673                     new_contents.append(line)
674
675             try:
676                 writefd = open(os.path.join(self._portdir, category, pkg_split[0], "%s.ebuild" % pkg), "w")
677                 writefd.write("".join(new_contents))
678                 writefd.close()
679             except IOError, e:
680                 error(e)
681                 sys.exit(1)
682         
683             os.chdir(os.path.join(self._portdir, category, pkg_split[0]))
684             do_cmd("echangelog 'Dependency update: %s -> %s.'" % (self._old_catpkg, self._new_catpkg))
685             do_cmd("%s 'Dependency update: %s -> %s.'" % (self._cvscmd["commit"], self._old_catpkg, self._new_catpkg))
686             self.__regen_manifest(path=os.path.join(self._portdir, category, pkg_split[0]), dep=True)
687             
688     def __get_reverse_deps(self):
689
690         dep_list = []
691         conf_portdir = "/usr/portage"
692
693         def scan_for_dep(arg, dir, files):
694
695             (null, category) = os.path.split(dir)
696
697             for file in files:
698                 if self._old_catpkg not in os.path.join(category, file):
699                     if not os.path.isdir(os.path.join(dir, file)):
700                         try:
701                             fd = open(os.path.join(dir, file), "r")
702                             contents = fd.readlines()
703                             fd.close()
704                         except IOError, e:
705                             error(e)
706                             sys.exit(1)
707                         if self._old_catpkg in contents[0] or self._old_catpkg in contents[1] or self._old_catpkg in contents[12]:
708                             dep_list.append(os.path.join(category, file))
709         
710         print "%s Resolving reverse dependencies..." % green("   *")
711
712         try:
713             fd = open("/etc/make.conf", "r")
714             contents = fd.readlines()
715             fd.close()
716         except IOError, e:
717             error(e)
718             sys.exit(1)
719         
720         for line in contents:
721             if line.startswith("PORTDIR="):
722                 (null, conf_portdir) = line.strip("\"\n").split("=")
723                 break
724
725         os.path.walk(os.path.join(conf_portdir, "metadata/cache"), scan_for_dep, None)
726
727         return dep_list
728
729     def __remove_old_package(self):
730
731         def remove_files(arg, dir, files):
732
733            if os.path.basename(dir) not in self._ignore:
734                for file in files: 
735                    if not os.path.isdir(os.path.join(dir, file)): 
736                        print "      <<< %s" % (os.path.join(dir.strip("./"), file))
737                        os.unlink(os.path.join(dir, file))
738                        do_cmd("%s %s" % (self._cvscmd["remove"], os.path.join(dir, file)))
739
740         print "%s Removing %s from CVS:" % (green("   *"), turquoise(self._old_catpkg))
741         os.chdir(os.path.join(self._portdir, self._old_catpkg))
742         os.path.walk('.', remove_files , None)
743         os.chdir("..")
744
745         print "%s Commiting changes..." % green("     *")
746         if self._options.remove:
747             explanation = ""
748             while(1):
749                 print "%s Please provide an explanation for this removal:" % yellow("       *"),
750                 try:
751                     explanation = raw_input("")
752                     explanation = explanation.replace("'", "\\'").replace('"', '\\"')
753                 except KeyboardInterrupt:
754                     print
755                     error("Interrupted by user.")
756                     sys.exit(1)
757                 if explanation != "":
758                     break
759             do_cmd("""%s "Removed from %s: %s" """ % (self._cvscmd["commit"], self._old_category, explanation))
760         else:
761             do_cmd("%s 'Moved to %s.'" % (self._cvscmd["commit"], self._new_catpkg))
762         do_cmd("rm -rf %s" % os.path.join(self._portdir, self._old_catpkg))
763
764         print "%s Checking for remnant files..." % (green("     *"))
765         do_cmd(self._cvscmd["update"])
766                                                                                                                   
767         if not os.path.exists(os.path.join(self._portdir, self._old_catpkg)):
768             if not self._action == "REMOVE":
769                 print "%s %s successfully removed from CVS." % (green("   *"), turquoise(self._old_catpkg))
770         else:
771             error("Remnants of %s still remain in CVS." % (turquoise(self._old_catpkg)))
772
773     def __add_new_package(self):
774
775         def add_files(arg, dir, files):
776
777             (null, null, null, dirs) = dir.split("/", 3)
778         
779             if os.path.basename(dir) not in self._ignore:
780                 os.chdir(os.path.join(self._portdir, self._new_category))
781                 if os.path.basename(dir) != self._new_package:
782                     print "      >>> %s/" % dirs   
783                     os.mkdir(dirs)
784                     do_cmd("%s %s" % (self._cvscmd["add"], dirs))
785                 for file in files:
786                     if not os.path.isdir(os.path.join(dir, file)):
787                         print "      >>> %s" % os.path.join(dirs, file)
788                         do_cmd("cp %s %s" % (os.path.join(dir, file), dirs))
789                         do_cmd("%s %s" % (self._cvscmd["add"], os.path.join(dirs, file)))
790
791         print "%s Adding %s to CVS:" % (green("   *"), turquoise(self._new_catpkg))
792         os.chdir(os.path.join(self._portdir, self._new_category))
793         print "      >>> %s/" % self._new_package
794         os.mkdir(self._new_package)
795         do_cmd("%s %s" % (self._cvscmd["add"], self._new_package))
796         os.chdir(self._new_package)
797         os.path.walk("/tmp/%s/%s" % (__productname__, self._new_package), add_files , None)
798         os.chdir(os.path.join(self._portdir, self._new_catpkg))
799
800         print "%s Adding ChangeLog entry..." % green("     *")
801         do_cmd("echangelog 'Moved from %s to %s.'" % (self._old_catpkg, self._new_catpkg))
802
803         print "%s Commiting changes..." % green("     *")
804         do_cmd("%s 'Moved from %s to %s.'" % (self._cvscmd["commit"], self._old_catpkg, self._new_catpkg))
805
806         print "%s %s successfully added to CVS." % (green("   *"), turquoise(self._new_catpkg))
807
808     def log_move(self):
809
810         if not self._action == "REMOVE":
811         
812             update_files = []
813             update_regex = "^[\d]Q-[\d]{4}$"
814
815             p_file_regex = re.compile(update_regex)
816
817             print "%s Logging move:" % (green("   *"))
818             os.chdir(os.path.join(self._portdir, "profiles/updates"))
819             do_cmd(self._cvscmd["update"])
820
821             for file in os.listdir("."):
822                 o_file_regex = p_file_regex.match(file)
823                 if file not in self._ignore and o_file_regex:
824                     (q, y) = file.split("-")
825                     update_files.append("%s-%s" % (y, q))
826
827             update_files.sort()
828             (y, q) = update_files[-1].split("-")
829             upfile = "%s-%s" % (q, y)
830             print "      >>> %s" % upfile
831
832             try:
833                 fd = open(upfile, "a")
834                 fd.write("move %s %s\n" % (self._old_catpkg, self._new_catpkg))
835                 fd.close()
836             except IOError, e:
837                 error(e)
838                 sys.exit(1)
839
840             do_cmd("%s 'Moved %s to %s'" % (self._cvscmd["commit"], self._old_catpkg, self._new_catpkg))
841             os.chdir(self._portdir)
842
843     def clean_up(self):
844
845         if not self._action == "REMOVE":
846             print "%s Removing back-up..." % (green("   *"))
847             do_cmd("rm -rf /tmp/%s/%s" % (__productname__, self._old_package))
848             if len(os.listdir("/tmp/%s" % __productname__)) == 0:
849                 do_cmd("rmdir /tmp/%s" % __productname__)
850
851         os.chdir(self._portdir)
852             
853 if __name__ == "__main__":
854     
855     signal.signal(signal.SIGINT, signal_handler)
856
857     cvscmd = {"remove": "cvs -Qf rm",
858               "commit": "cvs -Qf commit -m",
859               "update": "cvs -Qf up -dPC",
860               "add":    "cvs -Qf add"}
861
862     parser = OptionParser(usage="%prog [ option ] [ origin ] [ destination ]", version="%s-%s" % (__productname__, __version__))
863     parser.add_option("--usage", action="store_true", dest="usage", default=False, help="Pint usage information")
864     parser.add_option("-q", "--queue-check", action="store_true", dest="commit_queue", default=False, help="Check the cvs tree for files awaiting commit")
865     parser.add_option("-u", "--no-cvs-up", action="store_true", dest="cvs_up", default=False, help="Skip running cvs up in the origin and destination categories")
866     parser.add_option("-c", "--no-countdown", action="store_true", dest="countdown", default=False, help="Skip countdown before performing")
867     parser.add_option("-R", "--remove", action="store_true", dest="remove", default=False, help="Remove package")
868     parser.add_option("-F", "--force", action="store_true", dest="force", default=False, help="Force removal of package, ignoring any reverse deps")
869     (options, args) = parser.parse_args()
870
871     if options.usage:
872         print_usage()
873         sys.exit(0)
874
875     if randint(1, 100) == 50:
876         print "%s I put on my robe and wizard hat..." % green(" *")
877  
878     portdir = check_cwd(portage.settings["PORTDIR"].rstrip("/"))
879     check_repos(portdir)
880     (oldcatpkg, newcatpkg) = check_args(args, portdir, options, cvscmd)
881     
882     if options.commit_queue:
883         check_commit_queue()
884
885     ThisPackage = CVSAbstraction(portdir, oldcatpkg, newcatpkg, cvscmd, options)
886
887     ThisPackage.perform_action()
888     ThisPackage.log_move()
889     ThisPackage.clean_up()
890
891     if options.remove:
892         print "%s %s successfully removed from CVS." % (green(" *"), turquoise(oldcatpkg))
893     else:
894         print "%s %s successfully moved to %s." % (green(" *"), turquoise(oldcatpkg), yellow(newcatpkg))
895