1 """Basic quilt-like functionality
5 Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License version 2 as
9 published by the Free Software Foundation.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 from stgit.utils import *
24 from stgit import git, basedir, templates
25 from stgit.config import config
28 # stack exception class
29 class StackException(Exception):
34 self.should_print = True
35 def __call__(self, x, until_test, prefix):
37 self.should_print = False
39 return x[0:len(prefix)] != prefix
45 __comment_prefix = 'STG:'
46 __patch_prefix = 'STG_PATCH:'
48 def __clean_comments(f):
49 """Removes lines marked for status in a commit file
53 # remove status-prefixed lines
56 patch_filter = FilterUntil()
57 until_test = lambda t: t == (__patch_prefix + '\n')
58 lines = [l for l in lines if patch_filter(l, until_test, __comment_prefix)]
60 # remove empty lines at the end
61 while len(lines) != 0 and lines[-1] == '\n':
64 f.seek(0); f.truncate()
67 def edit_file(series, line, comment, show_patch = True):
68 fname = '.stgitmsg.txt'
69 tmpl = templates.get_template('patchdescr.tmpl')
78 print >> f, __comment_prefix, comment
79 print >> f, __comment_prefix, \
80 'Lines prefixed with "%s" will be automatically removed.' \
82 print >> f, __comment_prefix, \
83 'Trailing empty lines will be automatically removed.'
86 print >> f, __patch_prefix
87 # series.get_patch(series.get_current()).get_top()
88 git.diff([], series.get_patch(series.get_current()).get_bottom(), None, f)
90 #Vim modeline must be near the end.
91 print >> f, __comment_prefix, 'vi: set textwidth=75 filetype=diff nobackup:'
95 if config.has_option('stgit', 'editor'):
96 editor = config.get('stgit', 'editor')
97 elif 'EDITOR' in os.environ:
98 editor = os.environ['EDITOR']
101 editor += ' %s' % fname
103 print 'Invoking the editor: "%s"...' % editor,
105 print 'done (exit code: %d)' % os.system(editor)
107 f = file(fname, 'r+')
123 """Basic patch implementation
125 def __init__(self, name, series_dir, refs_dir):
126 self.__series_dir = series_dir
128 self.__dir = os.path.join(self.__series_dir, self.__name)
129 self.__refs_dir = refs_dir
130 self.__top_ref_file = os.path.join(self.__refs_dir, self.__name)
131 self.__log_ref_file = os.path.join(self.__refs_dir,
132 self.__name + '.log')
136 create_empty_file(os.path.join(self.__dir, 'bottom'))
137 create_empty_file(os.path.join(self.__dir, 'top'))
140 for f in os.listdir(self.__dir):
141 os.remove(os.path.join(self.__dir, f))
143 os.remove(self.__top_ref_file)
144 if os.path.exists(self.__log_ref_file):
145 os.remove(self.__log_ref_file)
150 def rename(self, newname):
152 old_top_ref_file = self.__top_ref_file
153 old_log_ref_file = self.__log_ref_file
154 self.__name = newname
155 self.__dir = os.path.join(self.__series_dir, self.__name)
156 self.__top_ref_file = os.path.join(self.__refs_dir, self.__name)
157 self.__log_ref_file = os.path.join(self.__refs_dir,
158 self.__name + '.log')
160 os.rename(olddir, self.__dir)
161 os.rename(old_top_ref_file, self.__top_ref_file)
162 if os.path.exists(old_log_ref_file):
163 os.rename(old_log_ref_file, self.__log_ref_file)
165 def __update_top_ref(self, ref):
166 write_string(self.__top_ref_file, ref)
168 def __update_log_ref(self, ref):
169 write_string(self.__log_ref_file, ref)
171 def update_top_ref(self):
174 self.__update_top_ref(top)
176 def __get_field(self, name, multiline = False):
177 id_file = os.path.join(self.__dir, name)
178 if os.path.isfile(id_file):
179 line = read_string(id_file, multiline)
187 def __set_field(self, name, value, multiline = False):
188 fname = os.path.join(self.__dir, name)
189 if value and value != '':
190 write_string(fname, value, multiline)
191 elif os.path.isfile(fname):
194 def get_old_bottom(self):
195 return self.__get_field('bottom.old')
197 def get_bottom(self):
198 return self.__get_field('bottom')
200 def set_bottom(self, value, backup = False):
202 curr = self.__get_field('bottom')
203 self.__set_field('bottom.old', curr)
204 self.__set_field('bottom', value)
206 def get_old_top(self):
207 return self.__get_field('top.old')
210 return self.__get_field('top')
212 def set_top(self, value, backup = False):
214 curr = self.__get_field('top')
215 self.__set_field('top.old', curr)
216 self.__set_field('top', value)
217 self.__update_top_ref(value)
219 def restore_old_boundaries(self):
220 bottom = self.__get_field('bottom.old')
221 top = self.__get_field('top.old')
224 self.__set_field('bottom', bottom)
225 self.__set_field('top', top)
226 self.__update_top_ref(top)
231 def get_description(self):
232 return self.__get_field('description', True)
234 def set_description(self, line):
235 self.__set_field('description', line, True)
237 def get_authname(self):
238 return self.__get_field('authname')
240 def set_authname(self, name):
242 if config.has_option('stgit', 'authname'):
243 name = config.get('stgit', 'authname')
244 elif 'GIT_AUTHOR_NAME' in os.environ:
245 name = os.environ['GIT_AUTHOR_NAME']
246 self.__set_field('authname', name)
248 def get_authemail(self):
249 return self.__get_field('authemail')
251 def set_authemail(self, address):
253 if config.has_option('stgit', 'authemail'):
254 address = config.get('stgit', 'authemail')
255 elif 'GIT_AUTHOR_EMAIL' in os.environ:
256 address = os.environ['GIT_AUTHOR_EMAIL']
257 self.__set_field('authemail', address)
259 def get_authdate(self):
260 return self.__get_field('authdate')
262 def set_authdate(self, date):
263 if not date and 'GIT_AUTHOR_DATE' in os.environ:
264 date = os.environ['GIT_AUTHOR_DATE']
265 self.__set_field('authdate', date)
267 def get_commname(self):
268 return self.__get_field('commname')
270 def set_commname(self, name):
272 if config.has_option('stgit', 'commname'):
273 name = config.get('stgit', 'commname')
274 elif 'GIT_COMMITTER_NAME' in os.environ:
275 name = os.environ['GIT_COMMITTER_NAME']
276 self.__set_field('commname', name)
278 def get_commemail(self):
279 return self.__get_field('commemail')
281 def set_commemail(self, address):
283 if config.has_option('stgit', 'commemail'):
284 address = config.get('stgit', 'commemail')
285 elif 'GIT_COMMITTER_EMAIL' in os.environ:
286 address = os.environ['GIT_COMMITTER_EMAIL']
287 self.__set_field('commemail', address)
290 return self.__get_field('log')
292 def set_log(self, value, backup = False):
293 self.__set_field('log', value)
294 self.__update_log_ref(value)
298 """Class including the operations on series
300 def __init__(self, name = None):
301 """Takes a series name as the parameter.
307 self.__name = git.get_head_file()
308 self.__base_dir = basedir.get()
309 except git.GitException, ex:
310 raise StackException, 'GIT tree not initialised: %s' % ex
312 self.__series_dir = os.path.join(self.__base_dir, 'patches',
314 self.__refs_dir = os.path.join(self.__base_dir, 'refs', 'patches',
316 self.__base_file = os.path.join(self.__base_dir, 'refs', 'bases',
319 self.__applied_file = os.path.join(self.__series_dir, 'applied')
320 self.__unapplied_file = os.path.join(self.__series_dir, 'unapplied')
321 self.__current_file = os.path.join(self.__series_dir, 'current')
322 self.__descr_file = os.path.join(self.__series_dir, 'description')
324 # where this series keeps its patches
325 self.__patch_dir = os.path.join(self.__series_dir, 'patches')
326 if not os.path.isdir(self.__patch_dir):
327 self.__patch_dir = self.__series_dir
329 # if no __refs_dir, create and populate it (upgrade old repositories)
330 if self.is_initialised() and not os.path.isdir(self.__refs_dir):
331 os.makedirs(self.__refs_dir)
332 for patch in self.get_applied() + self.get_unapplied():
333 self.get_patch(patch).update_top_ref()
336 self.__trash_dir = os.path.join(self.__series_dir, 'trash')
337 if self.is_initialised() and not os.path.isdir(self.__trash_dir):
338 os.makedirs(self.__trash_dir)
340 def get_branch(self):
341 """Return the branch name for the Series object
345 def __set_current(self, name):
346 """Sets the topmost patch
349 write_string(self.__current_file, name)
351 create_empty_file(self.__current_file)
353 def get_patch(self, name):
354 """Return a Patch object for the given name
356 return Patch(name, self.__patch_dir, self.__refs_dir)
358 def get_current_patch(self):
359 """Return a Patch object representing the topmost patch, or
360 None if there is no such patch."""
361 crt = self.get_current()
364 return Patch(crt, self.__patch_dir, self.__refs_dir)
366 def get_current(self):
367 """Return the name of the topmost patch, or None if there is
369 if os.path.isfile(self.__current_file):
370 name = read_string(self.__current_file)
378 def get_applied(self):
379 if not os.path.isfile(self.__applied_file):
380 raise StackException, 'Branch "%s" not initialised' % self.__name
381 f = file(self.__applied_file)
382 names = [line.strip() for line in f.readlines()]
386 def get_unapplied(self):
387 if not os.path.isfile(self.__unapplied_file):
388 raise StackException, 'Branch "%s" not initialised' % self.__name
389 f = file(self.__unapplied_file)
390 names = [line.strip() for line in f.readlines()]
394 def get_base_file(self):
395 self.__begin_stack_check()
396 return self.__base_file
398 def get_protected(self):
399 return os.path.isfile(os.path.join(self.__series_dir, 'protected'))
402 protect_file = os.path.join(self.__series_dir, 'protected')
403 if not os.path.isfile(protect_file):
404 create_empty_file(protect_file)
407 protect_file = os.path.join(self.__series_dir, 'protected')
408 if os.path.isfile(protect_file):
409 os.remove(protect_file)
411 def get_description(self):
412 if os.path.isfile(self.__descr_file):
413 return read_string(self.__descr_file)
417 def __patch_is_current(self, patch):
418 return patch.get_name() == read_string(self.__current_file)
420 def __patch_applied(self, name):
421 """Return true if the patch exists in the applied list
423 return name in self.get_applied()
425 def __patch_unapplied(self, name):
426 """Return true if the patch exists in the unapplied list
428 return name in self.get_unapplied()
430 def patch_exists(self, name):
431 """Return true if there is a patch with the given name, false
433 return self.__patch_applied(name) or self.__patch_unapplied(name)
435 def __begin_stack_check(self):
436 """Save the current HEAD into .git/refs/heads/base if the stack
439 if len(self.get_applied()) == 0:
440 head = git.get_head()
441 write_string(self.__base_file, head)
443 def __end_stack_check(self):
444 """Remove .git/refs/heads/base if the stack is empty.
445 This warning should never happen
447 if len(self.get_applied()) == 0 \
448 and read_string(self.__base_file) != git.get_head():
449 print 'Warning: stack empty but the HEAD and base are different'
451 def head_top_equal(self):
452 """Return true if the head and the top are the same
454 crt = self.get_current_patch()
456 # we don't care, no patches applied
458 return git.get_head() == crt.get_top()
460 def is_initialised(self):
461 """Checks if series is already initialised
463 return os.path.isdir(self.__patch_dir)
465 def init(self, create_at=False):
466 """Initialises the stgit series
468 bases_dir = os.path.join(self.__base_dir, 'refs', 'bases')
470 if os.path.exists(self.__patch_dir):
471 raise StackException, self.__patch_dir + ' already exists'
472 if os.path.exists(self.__refs_dir):
473 raise StackException, self.__refs_dir + ' already exists'
474 if os.path.exists(self.__base_file):
475 raise StackException, self.__base_file + ' already exists'
477 if (create_at!=False):
478 git.create_branch(self.__name, create_at)
480 os.makedirs(self.__patch_dir)
482 create_dirs(bases_dir)
484 create_empty_file(self.__applied_file)
485 create_empty_file(self.__unapplied_file)
486 create_empty_file(self.__descr_file)
487 os.makedirs(os.path.join(self.__series_dir, 'patches'))
488 os.makedirs(self.__refs_dir)
489 self.__begin_stack_check()
492 """Either convert to use a separate patch directory, or
493 unconvert to place the patches in the same directory with
496 if self.__patch_dir == self.__series_dir:
497 print 'Converting old-style to new-style...',
500 self.__patch_dir = os.path.join(self.__series_dir, 'patches')
501 os.makedirs(self.__patch_dir)
503 for p in self.get_applied() + self.get_unapplied():
504 src = os.path.join(self.__series_dir, p)
505 dest = os.path.join(self.__patch_dir, p)
511 print 'Converting new-style to old-style...',
514 for p in self.get_applied() + self.get_unapplied():
515 src = os.path.join(self.__patch_dir, p)
516 dest = os.path.join(self.__series_dir, p)
519 if not os.listdir(self.__patch_dir):
520 os.rmdir(self.__patch_dir)
523 print 'Patch directory %s is not empty.' % self.__name
525 self.__patch_dir = self.__series_dir
527 def rename(self, to_name):
530 to_stack = Series(to_name)
532 if to_stack.is_initialised():
533 raise StackException, '"%s" already exists' % to_stack.get_branch()
534 if os.path.exists(to_stack.__base_file):
535 os.remove(to_stack.__base_file)
537 git.rename_branch(self.__name, to_name)
539 if os.path.isdir(self.__series_dir):
540 rename(os.path.join(self.__base_dir, 'patches'),
541 self.__name, to_stack.__name)
542 if os.path.exists(self.__base_file):
543 rename(os.path.join(self.__base_dir, 'refs', 'bases'),
544 self.__name, to_stack.__name)
545 if os.path.exists(self.__refs_dir):
546 rename(os.path.join(self.__base_dir, 'refs', 'patches'),
547 self.__name, to_stack.__name)
549 self.__init__(to_name)
551 def clone(self, target_series):
554 base = read_string(self.get_base_file())
555 Series(target_series).init(create_at = base)
556 new_series = Series(target_series)
558 # generate an artificial description file
559 write_string(new_series.__descr_file, 'clone of "%s"' % self.__name)
561 # clone self's entire series as unapplied patches
562 patches = self.get_applied() + self.get_unapplied()
565 patch = self.get_patch(p)
566 new_series.new_patch(p, message = patch.get_description(),
567 can_edit = False, unapplied = True,
568 bottom = patch.get_bottom(),
569 top = patch.get_top(),
570 author_name = patch.get_authname(),
571 author_email = patch.get_authemail(),
572 author_date = patch.get_authdate())
574 # fast forward the cloned series to self's top
575 new_series.forward_patches(self.get_applied())
577 def delete(self, force = False):
578 """Deletes an stgit series
580 if self.is_initialised():
581 patches = self.get_unapplied() + self.get_applied()
582 if not force and patches:
583 raise StackException, \
584 'Cannot delete: the series still contains patches'
586 Patch(p, self.__patch_dir, self.__refs_dir).delete()
588 # remove the trash directory
589 for fname in os.listdir(self.__trash_dir):
591 os.rmdir(self.__trash_dir)
593 if os.path.exists(self.__applied_file):
594 os.remove(self.__applied_file)
595 if os.path.exists(self.__unapplied_file):
596 os.remove(self.__unapplied_file)
597 if os.path.exists(self.__current_file):
598 os.remove(self.__current_file)
599 if os.path.exists(self.__descr_file):
600 os.remove(self.__descr_file)
601 if not os.listdir(self.__patch_dir):
602 os.rmdir(self.__patch_dir)
604 print 'Patch directory %s is not empty.' % self.__name
605 if not os.listdir(self.__series_dir):
606 remove_dirs(os.path.join(self.__base_dir, 'patches'),
609 print 'Series directory %s is not empty.' % self.__name
610 if not os.listdir(self.__refs_dir):
611 remove_dirs(os.path.join(self.__base_dir, 'refs', 'patches'),
614 print 'Refs directory %s is not empty.' % self.__refs_dir
616 if os.path.exists(self.__base_file):
617 remove_file_and_dirs(
618 os.path.join(self.__base_dir, 'refs', 'bases'), self.__name)
620 def refresh_patch(self, files = None, message = None, edit = False,
623 author_name = None, author_email = None,
625 committer_name = None, committer_email = None,
626 backup = False, sign_str = None, log = 'refresh'):
627 """Generates a new commit for the given patch
629 name = self.get_current()
631 raise StackException, 'No patches applied'
633 patch = Patch(name, self.__patch_dir, self.__refs_dir)
635 descr = patch.get_description()
636 if not (message or descr):
642 if not message and edit:
643 descr = edit_file(self, descr.rstrip(), \
644 'Please edit the description for patch "%s" ' \
645 'above.' % name, show_patch)
648 author_name = patch.get_authname()
650 author_email = patch.get_authemail()
652 author_date = patch.get_authdate()
653 if not committer_name:
654 committer_name = patch.get_commname()
655 if not committer_email:
656 committer_email = patch.get_commemail()
659 descr = '%s\n%s: %s <%s>\n' % (descr.rstrip(), sign_str,
660 committer_name, committer_email)
662 bottom = patch.get_bottom()
664 commit_id = git.commit(files = files,
665 message = descr, parents = [bottom],
666 cache_update = cache_update,
668 author_name = author_name,
669 author_email = author_email,
670 author_date = author_date,
671 committer_name = committer_name,
672 committer_email = committer_email)
674 patch.set_bottom(bottom, backup = backup)
675 patch.set_top(commit_id, backup = backup)
676 patch.set_description(descr)
677 patch.set_authname(author_name)
678 patch.set_authemail(author_email)
679 patch.set_authdate(author_date)
680 patch.set_commname(committer_name)
681 patch.set_commemail(committer_email)
684 self.log_patch(patch, log)
688 def undo_refresh(self):
689 """Undo the patch boundaries changes caused by 'refresh'
691 name = self.get_current()
694 patch = Patch(name, self.__patch_dir, self.__refs_dir)
695 old_bottom = patch.get_old_bottom()
696 old_top = patch.get_old_top()
698 # the bottom of the patch is not changed by refresh. If the
699 # old_bottom is different, there wasn't any previous 'refresh'
700 # command (probably only a 'push')
701 if old_bottom != patch.get_bottom() or old_top == patch.get_top():
702 raise StackException, 'No refresh undo information available'
704 git.reset(tree_id = old_top, check_out = False)
705 if patch.restore_old_boundaries():
706 self.log_patch(patch, 'undo')
708 def new_patch(self, name, message = None, can_edit = True,
709 unapplied = False, show_patch = False,
710 top = None, bottom = None,
711 author_name = None, author_email = None, author_date = None,
712 committer_name = None, committer_email = None,
713 before_existing = False, refresh = True):
714 """Creates a new patch
716 if self.__patch_applied(name) or self.__patch_unapplied(name):
717 raise StackException, 'Patch "%s" already exists' % name
719 if not message and can_edit:
720 descr = edit_file(self, None, \
721 'Please enter the description for patch "%s" ' \
722 'above.' % name, show_patch)
726 head = git.get_head()
728 self.__begin_stack_check()
730 patch = Patch(name, self.__patch_dir, self.__refs_dir)
734 patch.set_bottom(bottom)
736 patch.set_bottom(head)
742 patch.set_description(descr)
743 patch.set_authname(author_name)
744 patch.set_authemail(author_email)
745 patch.set_authdate(author_date)
746 patch.set_commname(committer_name)
747 patch.set_commemail(committer_email)
750 self.log_patch(patch, 'new')
752 patches = [patch.get_name()] + self.get_unapplied()
754 f = file(self.__unapplied_file, 'w+')
755 f.writelines([line + '\n' for line in patches])
757 elif before_existing:
758 self.log_patch(patch, 'new')
760 insert_string(self.__applied_file, patch.get_name())
761 if not self.get_current():
762 self.__set_current(name)
764 append_string(self.__applied_file, patch.get_name())
765 self.__set_current(name)
767 self.refresh_patch(cache_update = False, log = 'new')
769 def delete_patch(self, name):
772 patch = Patch(name, self.__patch_dir, self.__refs_dir)
774 if self.__patch_is_current(patch):
776 elif self.__patch_applied(name):
777 raise StackException, 'Cannot remove an applied patch, "%s", ' \
778 'which is not current' % name
779 elif not name in self.get_unapplied():
780 raise StackException, 'Unknown patch "%s"' % name
782 # save the commit id to a trash file
783 write_string(os.path.join(self.__trash_dir, name), patch.get_top())
787 unapplied = self.get_unapplied()
788 unapplied.remove(name)
789 f = file(self.__unapplied_file, 'w+')
790 f.writelines([line + '\n' for line in unapplied])
792 self.__begin_stack_check()
794 def forward_patches(self, names):
795 """Try to fast-forward an array of patches.
797 On return, patches in names[0:returned_value] have been pushed on the
798 stack. Apply the rest with push_patch
800 unapplied = self.get_unapplied()
801 self.__begin_stack_check()
807 assert(name in unapplied)
809 patch = Patch(name, self.__patch_dir, self.__refs_dir)
812 bottom = patch.get_bottom()
813 top = patch.get_top()
815 # top != bottom always since we have a commit for each patch
817 # reset the backup information. No logging since the
818 # patch hasn't changed
819 patch.set_bottom(head, backup = True)
820 patch.set_top(top, backup = True)
823 head_tree = git.get_commit(head).get_tree()
824 bottom_tree = git.get_commit(bottom).get_tree()
825 if head_tree == bottom_tree:
826 # We must just reparent this patch and create a new commit
828 descr = patch.get_description()
829 author_name = patch.get_authname()
830 author_email = patch.get_authemail()
831 author_date = patch.get_authdate()
832 committer_name = patch.get_commname()
833 committer_email = patch.get_commemail()
835 top_tree = git.get_commit(top).get_tree()
837 top = git.commit(message = descr, parents = [head],
838 cache_update = False,
841 author_name = author_name,
842 author_email = author_email,
843 author_date = author_date,
844 committer_name = committer_name,
845 committer_email = committer_email)
847 patch.set_bottom(head, backup = True)
848 patch.set_top(top, backup = True)
850 self.log_patch(patch, 'push(f)')
853 # stop the fast-forwarding, must do a real merge
857 unapplied.remove(name)
864 append_strings(self.__applied_file, names[0:forwarded])
866 f = file(self.__unapplied_file, 'w+')
867 f.writelines([line + '\n' for line in unapplied])
870 self.__set_current(name)
874 def merged_patches(self, names):
875 """Test which patches were merged upstream by reverse-applying
876 them in reverse order. The function returns the list of
877 patches detected to have been applied. The state of the tree
878 is restored to the original one
880 patches = [Patch(name, self.__patch_dir, self.__refs_dir)
886 if git.apply_diff(p.get_top(), p.get_bottom()):
887 merged.append(p.get_name())
894 def push_patch(self, name, empty = False):
895 """Pushes a patch on the stack
897 unapplied = self.get_unapplied()
898 assert(name in unapplied)
900 self.__begin_stack_check()
902 patch = Patch(name, self.__patch_dir, self.__refs_dir)
904 head = git.get_head()
905 bottom = patch.get_bottom()
906 top = patch.get_top()
911 # top != bottom always since we have a commit for each patch
913 # just make an empty patch (top = bottom = HEAD). This
914 # option is useful to allow undoing already merged
915 # patches. The top is updated by refresh_patch since we
916 # need an empty commit
917 patch.set_bottom(head, backup = True)
918 patch.set_top(head, backup = True)
921 # reset the backup information. No need for logging
922 patch.set_bottom(bottom, backup = True)
923 patch.set_top(top, backup = True)
927 # new patch needs to be refreshed.
928 # The current patch is empty after merge.
929 patch.set_bottom(head, backup = True)
930 patch.set_top(head, backup = True)
932 # Try the fast applying first. If this fails, fall back to the
934 if not git.apply_diff(bottom, top):
935 # if git.apply_diff() fails, the patch requires a diff3
936 # merge and can be reported as modified
939 # merge can fail but the patch needs to be pushed
941 git.merge(bottom, head, top)
942 except git.GitException, ex:
943 print >> sys.stderr, \
944 'The merge failed during "push". ' \
945 'Use "refresh" after fixing the conflicts'
947 append_string(self.__applied_file, name)
949 unapplied.remove(name)
950 f = file(self.__unapplied_file, 'w+')
951 f.writelines([line + '\n' for line in unapplied])
954 self.__set_current(name)
956 # head == bottom case doesn't need to refresh the patch
957 if empty or head != bottom:
959 # if the merge was OK and no conflicts, just refresh the patch
960 # The GIT cache was already updated by the merge operation
965 self.refresh_patch(cache_update = False, log = log)
967 raise StackException, str(ex)
972 name = self.get_current()
975 patch = Patch(name, self.__patch_dir, self.__refs_dir)
976 old_bottom = patch.get_old_bottom()
977 old_top = patch.get_old_top()
979 # the top of the patch is changed by a push operation only
980 # together with the bottom (otherwise the top was probably
981 # modified by 'refresh'). If they are both unchanged, there
983 if old_bottom == patch.get_bottom() and old_top != patch.get_top():
984 raise StackException, 'No push undo information available'
988 ret = patch.restore_old_boundaries()
990 self.log_patch(patch, 'undo')
994 def pop_patch(self, name, keep = False):
995 """Pops the top patch from the stack
997 applied = self.get_applied()
999 assert(name in applied)
1001 patch = Patch(name, self.__patch_dir, self.__refs_dir)
1003 # only keep the local changes
1004 if keep and not git.apply_diff(git.get_head(), patch.get_bottom()):
1005 raise StackException, \
1006 'Failed to pop patches while preserving the local changes'
1008 git.switch(patch.get_bottom(), keep)
1010 # save the new applied list
1011 idx = applied.index(name) + 1
1013 popped = applied[:idx]
1015 unapplied = popped + self.get_unapplied()
1017 f = file(self.__unapplied_file, 'w+')
1018 f.writelines([line + '\n' for line in unapplied])
1024 f = file(self.__applied_file, 'w+')
1025 f.writelines([line + '\n' for line in applied])
1029 self.__set_current(None)
1031 self.__set_current(applied[-1])
1033 self.__end_stack_check()
1035 def empty_patch(self, name):
1036 """Returns True if the patch is empty
1038 patch = Patch(name, self.__patch_dir, self.__refs_dir)
1039 bottom = patch.get_bottom()
1040 top = patch.get_top()
1044 elif git.get_commit(top).get_tree() \
1045 == git.get_commit(bottom).get_tree():
1050 def rename_patch(self, oldname, newname):
1051 applied = self.get_applied()
1052 unapplied = self.get_unapplied()
1054 if oldname == newname:
1055 raise StackException, '"To" name and "from" name are the same'
1057 if newname in applied or newname in unapplied:
1058 raise StackException, 'Patch "%s" already exists' % newname
1060 if oldname in unapplied:
1061 Patch(oldname, self.__patch_dir, self.__refs_dir).rename(newname)
1062 unapplied[unapplied.index(oldname)] = newname
1064 f = file(self.__unapplied_file, 'w+')
1065 f.writelines([line + '\n' for line in unapplied])
1067 elif oldname in applied:
1068 Patch(oldname, self.__patch_dir, self.__refs_dir).rename(newname)
1069 if oldname == self.get_current():
1070 self.__set_current(newname)
1072 applied[applied.index(oldname)] = newname
1074 f = file(self.__applied_file, 'w+')
1075 f.writelines([line + '\n' for line in applied])
1078 raise StackException, 'Unknown patch "%s"' % oldname
1080 def log_patch(self, patch, message):
1081 """Generate a log commit for a patch
1083 top = git.get_commit(patch.get_top())
1084 msg = '%s\t%s' % (message, top.get_id_hash())
1086 old_log = patch.get_log()
1092 log = git.commit(message = msg, parents = parents,
1093 cache_update = False, tree_id = top.get_tree(),