78 """An unexpected state was encountered by an automated step.""" |
79 """An unexpected state was encountered by an automated step.""" |
79 |
80 |
80 |
81 |
81 class FileAccessError(Error): |
82 class FileAccessError(Error): |
82 """An error occured while accessing a file.""" |
83 """An error occured while accessing a file.""" |
83 |
|
84 |
|
85 def error(msg): |
|
86 """Log an error message.""" |
|
87 print util.colorize(msg, util.RED, bold=True) |
|
88 |
|
89 |
|
90 def info(msg): |
|
91 """Log an informational message.""" |
|
92 print util.colorize(msg, util.GREEN) |
|
93 |
84 |
94 |
85 |
95 def confirm(prompt, default=False): |
86 def confirm(prompt, default=False): |
96 """Ask a yes/no question and return the answer. |
87 """Ask a yes/no question and return the answer. |
97 |
88 |
119 elif answer in ('y', 'yes'): |
110 elif answer in ('y', 'yes'): |
120 return True |
111 return True |
121 elif answer in ('n', 'no'): |
112 elif answer in ('n', 'no'): |
122 return False |
113 return False |
123 else: |
114 else: |
124 error('Please answer yes or no.') |
115 log.error('Please answer yes or no.') |
125 |
116 |
126 |
117 |
127 def getString(prompt): |
118 def getString(prompt): |
128 """Prompt for and return a string.""" |
119 """Prompt for and return a string.""" |
129 try: |
120 try: |
140 while True: |
131 while True: |
141 value_str = getString(prompt) |
132 value_str = getString(prompt) |
142 try: |
133 try: |
143 return int(value_str) |
134 return int(value_str) |
144 except ValueError: |
135 except ValueError: |
145 error('Please enter a number. You entered "%s".' % value_str) |
136 log.error('Please enter a number. You entered "%s".' % value_str) |
146 |
137 |
147 |
138 |
148 def getChoice(intro, prompt, choices, done=None, suggest=None): |
139 def getChoice(intro, prompt, choices, done=None, suggest=None): |
149 """Prompt for and return a choice from a menu. |
140 """Prompt for and return a choice from a menu. |
150 |
141 |
172 print '%s%2d. %s%s' % (indent, i+1, entry, done_text) |
163 print '%s%2d. %s%s' % (indent, i+1, entry, done_text) |
173 print |
164 print |
174 choice = getNumber(prompt) |
165 choice = getNumber(prompt) |
175 if 0 < choice <= len(choices): |
166 if 0 < choice <= len(choices): |
176 return choice-1 |
167 return choice-1 |
177 error('%d is not a valid choice between %d and %d' % |
168 log.error('%d is not a valid choice between %d and %d' % |
178 (choice, 1, len(choices))) |
169 (choice, 1, len(choices))) |
179 print |
170 print |
180 |
171 |
181 |
172 |
182 def fileToLines(path): |
173 def fileToLines(path): |
183 """Read a file and return it as a list of lines.""" |
174 """Read a file and return it as a list of lines.""" |
542 |
533 |
543 Will also select the latest release branch, if any, so that |
534 Will also select the latest release branch, if any, so that |
544 the end state is a fully ready to function release |
535 the end state is a fully ready to function release |
545 environment. |
536 environment. |
546 """ |
537 """ |
547 info('Checking out the release repository') |
538 log.info('Checking out the release repository') |
548 |
539 |
549 # Check out a sparse view of the relevant repository paths. |
540 # Check out a sparse view of the relevant repository paths. |
550 self.wc.checkout(self.release_repos, depth='immediates') |
541 self.wc.checkout(self.release_repos, depth='immediates') |
551 self.wc.update('vendor', depth='immediates') |
542 self.wc.update('vendor', depth='immediates') |
552 self.wc.update('branches', depth='immediates') |
543 self.wc.update('branches', depth='immediates') |
596 |
587 |
597 """ |
588 """ |
598 if release is None: |
589 if release is None: |
599 self.branch = None |
590 self.branch = None |
600 self.branch_dir = None |
591 self.branch_dir = None |
601 info('No release branch available') |
592 log.info('No release branch available') |
602 else: |
593 else: |
603 self.wc.update() |
594 self.wc.update() |
604 assert self.wc.exists('branches/' + release) |
595 assert self.wc.exists('branches/' + release) |
605 linesToFile(self.path(self.BRANCH_FILE), [release]) |
596 linesToFile(self.path(self.BRANCH_FILE), [release]) |
606 self.branch = release |
597 self.branch = release |
607 self.branch_dir = 'branches/' + release |
598 self.branch_dir = 'branches/' + release |
608 self.wc.update(self.branch_dir, depth='infinity') |
599 self.wc.update(self.branch_dir, depth='infinity') |
609 info('Working on branch ' + self.branch) |
600 log.info('Working on branch ' + self.branch) |
610 |
601 |
611 def _branchPath(self, path): |
602 def _branchPath(self, path): |
612 """Return the given path with the release branch path prepended.""" |
603 """Return the given path with the release branch path prepended.""" |
613 assert self.branch_dir is not None |
604 assert self.branch_dir is not None |
614 return os.path.join(self.branch_dir, path) |
605 return os.path.join(self.branch_dir, path) |
727 # google-specific patches. |
718 # google-specific patches. |
728 self._addAppYaml() |
719 self._addAppYaml() |
729 self._applyGooglePatches() |
720 self._applyGooglePatches() |
730 |
721 |
731 # All done! |
722 # All done! |
732 info('Melange release %s imported and googlified' % self.branch) |
723 log.info('Melange release %s imported and googlified' % self.branch) |
733 |
724 |
734 @requires_branch |
725 @requires_branch |
735 @pristine_wc |
726 @pristine_wc |
736 def cherryPickChange(self): |
727 def cherryPickChange(self): |
737 """Cherry-pick a change from the Melange trunk""" |
728 """Cherry-pick a change from the Melange trunk""" |
762 updated_patchlevel = True |
753 updated_patchlevel = True |
763 else: |
754 else: |
764 out.append(line) |
755 out.append(line) |
765 |
756 |
766 if not updated_patchlevel: |
757 if not updated_patchlevel: |
767 error('Failed to update Google patch revision') |
758 log.error('Failed to update Google patch revision') |
768 error('Cherry-picking failed') |
759 log.error('Cherry-picking failed') |
769 |
760 |
770 linesToFile(yaml_path, out) |
761 linesToFile(yaml_path, out) |
771 |
762 |
772 info('Check the diff about to be committed with:') |
763 log.info('Check the diff about to be committed with:') |
773 info('svn diff ' + self.wc.path(self.branch_dir)) |
764 log.info('svn diff ' + self.wc.path(self.branch_dir)) |
774 if not confirm('Commit this change?'): |
765 if not confirm('Commit this change?'): |
775 raise AbortedByUser('Cherry-pick aborted') |
766 raise AbortedByUser('Cherry-pick aborted') |
776 self.wc.commit(message) |
767 self.wc.commit(message) |
777 info('Cherry-picked r%d from the Melange trunk.' % rev) |
768 log.info('Cherry-picked r%d from the Melange trunk.' % rev) |
778 |
769 |
779 MENU_ORDER = [ |
770 MENU_ORDER = [ |
780 update, |
771 update, |
781 switchToBranch, |
772 switchToBranch, |
782 importTag, |
773 importTag, |
811 try: |
802 try: |
812 choice = getChoice('Main menu:', 'Your choice?', |
803 choice = getChoice('Main menu:', 'Your choice?', |
813 self.MENU_STRINGS, done=done, |
804 self.MENU_STRINGS, done=done, |
814 suggest=suggested_next) |
805 suggest=suggested_next) |
815 except (KeyboardInterrupt, AbortedByUser): |
806 except (KeyboardInterrupt, AbortedByUser): |
816 info('Exiting.') |
807 log.info('Exiting.') |
817 return |
808 return |
818 try: |
809 try: |
819 self.MENU_ORDER[choice](self) |
810 self.MENU_ORDER[choice](self) |
820 except Error, e: |
811 except Error, e: |
821 error(str(e)) |
812 log.error(str(e)) |
822 else: |
813 else: |
823 done.append(choice) |
814 done.append(choice) |
824 last_choice = choice |
815 last_choice = choice |
825 |
816 |
826 |
817 |
834 if len(argv) >= 2: |
825 if len(argv) >= 2: |
835 release_repos = argv[1] |
826 release_repos = argv[1] |
836 if len(argv) == 3: |
827 if len(argv) == 3: |
837 upstream_repos = argv[2] |
828 upstream_repos = argv[2] |
838 |
829 |
839 info('Release repository: ' + release_repos) |
830 log.init('release.log') |
840 info('Upstream repository: ' + upstream_repos) |
831 |
|
832 log.info('Release repository: ' + release_repos) |
|
833 log.info('Upstream repository: ' + upstream_repos) |
841 |
834 |
842 r = ReleaseEnvironment(os.path.abspath('_release_'), |
835 r = ReleaseEnvironment(os.path.abspath('_release_'), |
843 release_repos, |
836 release_repos, |
844 upstream_repos) |
837 upstream_repos) |
845 r.interactiveMenu() |
838 r.interactiveMenu() |