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()
335 def get_branch(self):
336 """Return the branch name for the Series object
340 def __set_current(self, name):
341 """Sets the topmost patch
344 write_string(self.__current_file, name)
346 create_empty_file(self.__current_file)
348 def get_patch(self, name):
349 """Return a Patch object for the given name
351 return Patch(name, self.__patch_dir, self.__refs_dir)
353 def get_current_patch(self):
354 """Return a Patch object representing the topmost patch, or
355 None if there is no such patch."""
356 crt = self.get_current()
359 return Patch(crt, self.__patch_dir, self.__refs_dir)
361 def get_current(self):
362 """Return the name of the topmost patch, or None if there is
364 if os.path.isfile(self.__current_file):
365 name = read_string(self.__current_file)
373 def get_applied(self):
374 if not os.path.isfile(self.__applied_file):
375 raise StackException, 'Branch "%s" not initialised' % self.__name
376 f = file(self.__applied_file)
377 names = [line.strip() for line in f.readlines()]
381 def get_unapplied(self):
382 if not os.path.isfile(self.__unapplied_file):
383 raise StackException, 'Branch "%s" not initialised' % self.__name
384 f = file(self.__unapplied_file)
385 names = [line.strip() for line in f.readlines()]
389 def get_base_file(self):
390 self.__begin_stack_check()
391 return self.__base_file
393 def get_protected(self):
394 return os.path.isfile(os.path.join(self.__series_dir, 'protected'))
397 protect_file = os.path.join(self.__series_dir, 'protected')
398 if not os.path.isfile(protect_file):
399 create_empty_file(protect_file)
402 protect_file = os.path.join(self.__series_dir, 'protected')
403 if os.path.isfile(protect_file):
404 os.remove(protect_file)
406 def get_description(self):
407 if os.path.isfile(self.__descr_file):
408 return read_string(self.__descr_file)
412 def __patch_is_current(self, patch):
413 return patch.get_name() == read_string(self.__current_file)
415 def __patch_applied(self, name):
416 """Return true if the patch exists in the applied list
418 return name in self.get_applied()
420 def __patch_unapplied(self, name):
421 """Return true if the patch exists in the unapplied list
423 return name in self.get_unapplied()
425 def patch_exists(self, name):
426 """Return true if there is a patch with the given name, false
428 return self.__patch_applied(name) or self.__patch_applied(name)
430 def __begin_stack_check(self):
431 """Save the current HEAD into .git/refs/heads/base if the stack
434 if len(self.get_applied()) == 0:
435 head = git.get_head()
436 write_string(self.__base_file, head)
438 def __end_stack_check(self):
439 """Remove .git/refs/heads/base if the stack is empty.
440 This warning should never happen
442 if len(self.get_applied()) == 0 \
443 and read_string(self.__base_file) != git.get_head():
444 print 'Warning: stack empty but the HEAD and base are different'
446 def head_top_equal(self):
447 """Return true if the head and the top are the same
449 crt = self.get_current_patch()
451 # we don't care, no patches applied
453 return git.get_head() == crt.get_top()
455 def is_initialised(self):
456 """Checks if series is already initialised
458 return os.path.isdir(self.__patch_dir)
460 def init(self, create_at=False):
461 """Initialises the stgit series
463 bases_dir = os.path.join(self.__base_dir, 'refs', 'bases')
465 if os.path.exists(self.__patch_dir):
466 raise StackException, self.__patch_dir + ' already exists'
467 if os.path.exists(self.__refs_dir):
468 raise StackException, self.__refs_dir + ' already exists'
469 if os.path.exists(self.__base_file):
470 raise StackException, self.__base_file + ' already exists'
472 if (create_at!=False):
473 git.create_branch(self.__name, create_at)
475 os.makedirs(self.__patch_dir)
477 create_dirs(bases_dir)
479 create_empty_file(self.__applied_file)
480 create_empty_file(self.__unapplied_file)
481 create_empty_file(self.__descr_file)
482 os.makedirs(os.path.join(self.__series_dir, 'patches'))
483 os.makedirs(self.__refs_dir)
484 self.__begin_stack_check()
487 """Either convert to use a separate patch directory, or
488 unconvert to place the patches in the same directory with
491 if self.__patch_dir == self.__series_dir:
492 print 'Converting old-style to new-style...',
495 self.__patch_dir = os.path.join(self.__series_dir, 'patches')
496 os.makedirs(self.__patch_dir)
498 for p in self.get_applied() + self.get_unapplied():
499 src = os.path.join(self.__series_dir, p)
500 dest = os.path.join(self.__patch_dir, p)
506 print 'Converting new-style to old-style...',
509 for p in self.get_applied() + self.get_unapplied():
510 src = os.path.join(self.__patch_dir, p)
511 dest = os.path.join(self.__series_dir, p)
514 if not os.listdir(self.__patch_dir):
515 os.rmdir(self.__patch_dir)
518 print 'Patch directory %s is not empty.' % self.__name
520 self.__patch_dir = self.__series_dir
522 def rename(self, to_name):
525 to_stack = Series(to_name)
527 if to_stack.is_initialised():
528 raise StackException, '"%s" already exists' % to_stack.get_branch()
529 if os.path.exists(to_stack.__base_file):
530 os.remove(to_stack.__base_file)
532 git.rename_branch(self.__name, to_name)
534 if os.path.isdir(self.__series_dir):
535 rename(os.path.join(self.__base_dir, 'patches'),
536 self.__name, to_stack.__name)
537 if os.path.exists(self.__base_file):
538 rename(os.path.join(self.__base_dir, 'refs', 'bases'),
539 self.__name, to_stack.__name)
540 if os.path.exists(self.__refs_dir):
541 rename(os.path.join(self.__base_dir, 'refs', 'patches'),
542 self.__name, to_stack.__name)
544 self.__init__(to_name)
546 def clone(self, target_series):
549 base = read_string(self.get_base_file())
550 Series(target_series).init(create_at = base)
551 new_series = Series(target_series)
553 # generate an artificial description file
554 write_string(new_series.__descr_file, 'clone of "%s"' % self.__name)
556 # clone self's entire series as unapplied patches
557 patches = self.get_applied() + self.get_unapplied()
560 patch = self.get_patch(p)
561 new_series.new_patch(p, message = patch.get_description(),
562 can_edit = False, unapplied = True,
563 bottom = patch.get_bottom(),
564 top = patch.get_top(),
565 author_name = patch.get_authname(),
566 author_email = patch.get_authemail(),
567 author_date = patch.get_authdate())
569 # fast forward the cloned series to self's top
570 new_series.forward_patches(self.get_applied())
572 def delete(self, force = False):
573 """Deletes an stgit series
575 if self.is_initialised():
576 patches = self.get_unapplied() + self.get_applied()
577 if not force and patches:
578 raise StackException, \
579 'Cannot delete: the series still contains patches'
581 Patch(p, self.__patch_dir, self.__refs_dir).delete()
583 if os.path.exists(self.__applied_file):
584 os.remove(self.__applied_file)
585 if os.path.exists(self.__unapplied_file):
586 os.remove(self.__unapplied_file)
587 if os.path.exists(self.__current_file):
588 os.remove(self.__current_file)
589 if os.path.exists(self.__descr_file):
590 os.remove(self.__descr_file)
591 if not os.listdir(self.__patch_dir):
592 os.rmdir(self.__patch_dir)
594 print 'Patch directory %s is not empty.' % self.__name
595 if not os.listdir(self.__series_dir):
596 remove_dirs(os.path.join(self.__base_dir, 'patches'),
599 print 'Series directory %s is not empty.' % self.__name
600 if not os.listdir(self.__refs_dir):
601 remove_dirs(os.path.join(self.__base_dir, 'refs', 'patches'),
604 print 'Refs directory %s is not empty.' % self.__refs_dir
606 if os.path.exists(self.__base_file):
607 remove_file_and_dirs(
608 os.path.join(self.__base_dir, 'refs', 'bases'), self.__name)
610 def refresh_patch(self, files = None, message = None, edit = False,
613 author_name = None, author_email = None,
615 committer_name = None, committer_email = None,
616 backup = False, sign_str = None, log = 'refresh'):
617 """Generates a new commit for the given patch
619 name = self.get_current()
621 raise StackException, 'No patches applied'
623 patch = Patch(name, self.__patch_dir, self.__refs_dir)
625 descr = patch.get_description()
626 if not (message or descr):
632 if not message and edit:
633 descr = edit_file(self, descr.rstrip(), \
634 'Please edit the description for patch "%s" ' \
635 'above.' % name, show_patch)
638 author_name = patch.get_authname()
640 author_email = patch.get_authemail()
642 author_date = patch.get_authdate()
643 if not committer_name:
644 committer_name = patch.get_commname()
645 if not committer_email:
646 committer_email = patch.get_commemail()
649 descr = '%s\n%s: %s <%s>\n' % (descr.rstrip(), sign_str,
650 committer_name, committer_email)
652 bottom = patch.get_bottom()
654 commit_id = git.commit(files = files,
655 message = descr, parents = [bottom],
656 cache_update = cache_update,
658 author_name = author_name,
659 author_email = author_email,
660 author_date = author_date,
661 committer_name = committer_name,
662 committer_email = committer_email)
664 patch.set_bottom(bottom, backup = backup)
665 patch.set_top(commit_id, backup = backup)
666 patch.set_description(descr)
667 patch.set_authname(author_name)
668 patch.set_authemail(author_email)
669 patch.set_authdate(author_date)
670 patch.set_commname(committer_name)
671 patch.set_commemail(committer_email)
674 self.log_patch(patch, log)
678 def undo_refresh(self):
679 """Undo the patch boundaries changes caused by 'refresh'
681 name = self.get_current()
684 patch = Patch(name, self.__patch_dir, self.__refs_dir)
685 old_bottom = patch.get_old_bottom()
686 old_top = patch.get_old_top()
688 # the bottom of the patch is not changed by refresh. If the
689 # old_bottom is different, there wasn't any previous 'refresh'
690 # command (probably only a 'push')
691 if old_bottom != patch.get_bottom() or old_top == patch.get_top():
692 raise StackException, 'No refresh undo information available'
694 git.reset(tree_id = old_top, check_out = False)
695 if patch.restore_old_boundaries():
696 self.log_patch(patch, 'undo')
698 def new_patch(self, name, message = None, can_edit = True,
699 unapplied = False, show_patch = False,
700 top = None, bottom = None,
701 author_name = None, author_email = None, author_date = None,
702 committer_name = None, committer_email = None,
703 before_existing = False, refresh = True):
704 """Creates a new patch
706 if self.__patch_applied(name) or self.__patch_unapplied(name):
707 raise StackException, 'Patch "%s" already exists' % name
709 if not message and can_edit:
710 descr = edit_file(self, None, \
711 'Please enter the description for patch "%s" ' \
712 'above.' % name, show_patch)
716 head = git.get_head()
718 self.__begin_stack_check()
720 patch = Patch(name, self.__patch_dir, self.__refs_dir)
724 patch.set_bottom(bottom)
726 patch.set_bottom(head)
732 patch.set_description(descr)
733 patch.set_authname(author_name)
734 patch.set_authemail(author_email)
735 patch.set_authdate(author_date)
736 patch.set_commname(committer_name)
737 patch.set_commemail(committer_email)
740 self.log_patch(patch, 'new')
742 patches = [patch.get_name()] + self.get_unapplied()
744 f = file(self.__unapplied_file, 'w+')
745 f.writelines([line + '\n' for line in patches])
747 elif before_existing:
748 self.log_patch(patch, 'new')
750 insert_string(self.__applied_file, patch.get_name())
751 if not self.get_current():
752 self.__set_current(name)
754 append_string(self.__applied_file, patch.get_name())
755 self.__set_current(name)
757 self.refresh_patch(cache_update = False, log = 'new')
759 def delete_patch(self, name):
762 patch = Patch(name, self.__patch_dir, self.__refs_dir)
764 if self.__patch_is_current(patch):
766 elif self.__patch_applied(name):
767 raise StackException, 'Cannot remove an applied patch, "%s", ' \
768 'which is not current' % name
769 elif not name in self.get_unapplied():
770 raise StackException, 'Unknown patch "%s"' % name
774 unapplied = self.get_unapplied()
775 unapplied.remove(name)
776 f = file(self.__unapplied_file, 'w+')
777 f.writelines([line + '\n' for line in unapplied])
779 self.__begin_stack_check()
781 def forward_patches(self, names):
782 """Try to fast-forward an array of patches.
784 On return, patches in names[0:returned_value] have been pushed on the
785 stack. Apply the rest with push_patch
787 unapplied = self.get_unapplied()
788 self.__begin_stack_check()
794 assert(name in unapplied)
796 patch = Patch(name, self.__patch_dir, self.__refs_dir)
799 bottom = patch.get_bottom()
800 top = patch.get_top()
802 # top != bottom always since we have a commit for each patch
804 # reset the backup information. No logging since the
805 # patch hasn't changed
806 patch.set_bottom(head, backup = True)
807 patch.set_top(top, backup = True)
810 head_tree = git.get_commit(head).get_tree()
811 bottom_tree = git.get_commit(bottom).get_tree()
812 if head_tree == bottom_tree:
813 # We must just reparent this patch and create a new commit
815 descr = patch.get_description()
816 author_name = patch.get_authname()
817 author_email = patch.get_authemail()
818 author_date = patch.get_authdate()
819 committer_name = patch.get_commname()
820 committer_email = patch.get_commemail()
822 top_tree = git.get_commit(top).get_tree()
824 top = git.commit(message = descr, parents = [head],
825 cache_update = False,
828 author_name = author_name,
829 author_email = author_email,
830 author_date = author_date,
831 committer_name = committer_name,
832 committer_email = committer_email)
834 patch.set_bottom(head, backup = True)
835 patch.set_top(top, backup = True)
837 self.log_patch(patch, 'push(f)')
840 # stop the fast-forwarding, must do a real merge
844 unapplied.remove(name)
851 append_strings(self.__applied_file, names[0:forwarded])
853 f = file(self.__unapplied_file, 'w+')
854 f.writelines([line + '\n' for line in unapplied])
857 self.__set_current(name)
861 def merged_patches(self, names):
862 """Test which patches were merged upstream by reverse-applying
863 them in reverse order. The function returns the list of
864 patches detected to have been applied. The state of the tree
865 is restored to the original one
867 patches = [Patch(name, self.__patch_dir, self.__refs_dir)
873 if git.apply_diff(p.get_top(), p.get_bottom()):
874 merged.append(p.get_name())
881 def push_patch(self, name, empty = False):
882 """Pushes a patch on the stack
884 unapplied = self.get_unapplied()
885 assert(name in unapplied)
887 self.__begin_stack_check()
889 patch = Patch(name, self.__patch_dir, self.__refs_dir)
891 head = git.get_head()
892 bottom = patch.get_bottom()
893 top = patch.get_top()
898 # top != bottom always since we have a commit for each patch
900 # just make an empty patch (top = bottom = HEAD). This
901 # option is useful to allow undoing already merged
902 # patches. The top is updated by refresh_patch since we
903 # need an empty commit
904 patch.set_bottom(head, backup = True)
905 patch.set_top(head, backup = True)
908 # reset the backup information. No need for logging
909 patch.set_bottom(bottom, backup = True)
910 patch.set_top(top, backup = True)
914 # new patch needs to be refreshed.
915 # The current patch is empty after merge.
916 patch.set_bottom(head, backup = True)
917 patch.set_top(head, backup = True)
919 # Try the fast applying first. If this fails, fall back to the
921 if not git.apply_diff(bottom, top):
922 # if git.apply_diff() fails, the patch requires a diff3
923 # merge and can be reported as modified
926 # merge can fail but the patch needs to be pushed
928 git.merge(bottom, head, top)
929 except git.GitException, ex:
930 print >> sys.stderr, \
931 'The merge failed during "push". ' \
932 'Use "refresh" after fixing the conflicts'
934 append_string(self.__applied_file, name)
936 unapplied.remove(name)
937 f = file(self.__unapplied_file, 'w+')
938 f.writelines([line + '\n' for line in unapplied])
941 self.__set_current(name)
943 # head == bottom case doesn't need to refresh the patch
944 if empty or head != bottom:
946 # if the merge was OK and no conflicts, just refresh the patch
947 # The GIT cache was already updated by the merge operation
952 self.refresh_patch(cache_update = False, log = log)
954 raise StackException, str(ex)
959 name = self.get_current()
962 patch = Patch(name, self.__patch_dir, self.__refs_dir)
963 old_bottom = patch.get_old_bottom()
964 old_top = patch.get_old_top()
966 # the top of the patch is changed by a push operation only
967 # together with the bottom (otherwise the top was probably
968 # modified by 'refresh'). If they are both unchanged, there
970 if old_bottom == patch.get_bottom() and old_top != patch.get_top():
971 raise StackException, 'No push undo information available'
975 ret = patch.restore_old_boundaries()
977 self.log_patch(patch, 'undo')
981 def pop_patch(self, name, keep = False):
982 """Pops the top patch from the stack
984 applied = self.get_applied()
986 assert(name in applied)
988 patch = Patch(name, self.__patch_dir, self.__refs_dir)
990 # only keep the local changes
991 if keep and not git.apply_diff(git.get_head(), patch.get_bottom()):
992 raise StackException, \
993 'Failed to pop patches while preserving the local changes'
995 git.switch(patch.get_bottom(), keep)
997 # save the new applied list
998 idx = applied.index(name) + 1
1000 popped = applied[:idx]
1002 unapplied = popped + self.get_unapplied()
1004 f = file(self.__unapplied_file, 'w+')
1005 f.writelines([line + '\n' for line in unapplied])
1011 f = file(self.__applied_file, 'w+')
1012 f.writelines([line + '\n' for line in applied])
1016 self.__set_current(None)
1018 self.__set_current(applied[-1])
1020 self.__end_stack_check()
1022 def empty_patch(self, name):
1023 """Returns True if the patch is empty
1025 patch = Patch(name, self.__patch_dir, self.__refs_dir)
1026 bottom = patch.get_bottom()
1027 top = patch.get_top()
1031 elif git.get_commit(top).get_tree() \
1032 == git.get_commit(bottom).get_tree():
1037 def rename_patch(self, oldname, newname):
1038 applied = self.get_applied()
1039 unapplied = self.get_unapplied()
1041 if oldname == newname:
1042 raise StackException, '"To" name and "from" name are the same'
1044 if newname in applied or newname in unapplied:
1045 raise StackException, 'Patch "%s" already exists' % newname
1047 if oldname in unapplied:
1048 Patch(oldname, self.__patch_dir, self.__refs_dir).rename(newname)
1049 unapplied[unapplied.index(oldname)] = newname
1051 f = file(self.__unapplied_file, 'w+')
1052 f.writelines([line + '\n' for line in unapplied])
1054 elif oldname in applied:
1055 Patch(oldname, self.__patch_dir, self.__refs_dir).rename(newname)
1056 if oldname == self.get_current():
1057 self.__set_current(newname)
1059 applied[applied.index(oldname)] = newname
1061 f = file(self.__applied_file, 'w+')
1062 f.writelines([line + '\n' for line in applied])
1065 raise StackException, 'Unknown patch "%s"' % oldname
1067 def log_patch(self, patch, message):
1068 """Generate a log commit for a patch
1070 top = git.get_commit(patch.get_top())
1071 msg = '%s\t%s' % (message, top.get_id_hash())
1073 old_log = patch.get_log()
1079 log = git.commit(message = msg, parents = parents,
1080 cache_update = False, tree_id = top.get_tree(),