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
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):
69 tmpl
= os
.path
.join(basedir
.get(), 'patchdescr.tmpl')
74 elif os
.path
.isfile(tmpl
):
75 print >> f
, file(tmpl
).read().rstrip()
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
)
134 create_empty_file(os
.path
.join(self
.__dir
, 'bottom'))
135 create_empty_file(os
.path
.join(self
.__dir
, 'top'))
138 for f
in os
.listdir(self
.__dir
):
139 os
.remove(os
.path
.join(self
.__dir
, f
))
141 os
.remove(self
.__top_ref_file
)
146 def rename(self
, newname
):
148 old_ref_file
= self
.__top_ref_file
149 self
.__name
= newname
150 self
.__dir
= os
.path
.join(self
.__series_dir
, self
.__name
)
151 self
.__top_ref_file
= os
.path
.join(self
.__refs_dir
, self
.__name
)
153 os
.rename(olddir
, self
.__dir
)
154 os
.rename(old_ref_file
, self
.__top_ref_file
)
156 def __update_top_ref(self
, ref
):
157 write_string(self
.__top_ref_file
, ref
)
159 def update_top_ref(self
):
162 self
.__update_top_ref(top
)
164 def __get_field(self
, name
, multiline
= False):
165 id_file
= os
.path
.join(self
.__dir
, name
)
166 if os
.path
.isfile(id_file
):
167 line
= read_string(id_file
, multiline
)
175 def __set_field(self
, name
, value
, multiline
= False):
176 fname
= os
.path
.join(self
.__dir
, name
)
177 if value
and value
!= '':
178 write_string(fname
, value
, multiline
)
179 elif os
.path
.isfile(fname
):
182 def get_old_bottom(self
):
183 return self
.__get_field('bottom.old')
185 def get_bottom(self
):
186 return self
.__get_field('bottom')
188 def set_bottom(self
, value
, backup
= False):
190 curr
= self
.__get_field('bottom')
191 self
.__set_field('bottom.old', curr
)
192 self
.__set_field('bottom', value
)
194 def get_old_top(self
):
195 return self
.__get_field('top.old')
198 return self
.__get_field('top')
200 def set_top(self
, value
, backup
= False):
202 curr
= self
.__get_field('top')
203 self
.__set_field('top.old', curr
)
204 self
.__set_field('top', value
)
205 self
.__update_top_ref(value
)
207 def restore_old_boundaries(self
):
208 bottom
= self
.__get_field('bottom.old')
209 top
= self
.__get_field('top.old')
212 self
.__set_field('bottom', bottom
)
213 self
.__set_field('top', top
)
214 self
.__update_top_ref(top
)
219 def get_description(self
):
220 return self
.__get_field('description', True)
222 def set_description(self
, line
):
223 self
.__set_field('description', line
, True)
225 def get_authname(self
):
226 return self
.__get_field('authname')
228 def set_authname(self
, name
):
230 if config
.has_option('stgit', 'authname'):
231 name
= config
.get('stgit', 'authname')
232 elif 'GIT_AUTHOR_NAME' in os
.environ
:
233 name
= os
.environ
['GIT_AUTHOR_NAME']
234 self
.__set_field('authname', name
)
236 def get_authemail(self
):
237 return self
.__get_field('authemail')
239 def set_authemail(self
, address
):
241 if config
.has_option('stgit', 'authemail'):
242 address
= config
.get('stgit', 'authemail')
243 elif 'GIT_AUTHOR_EMAIL' in os
.environ
:
244 address
= os
.environ
['GIT_AUTHOR_EMAIL']
245 self
.__set_field('authemail', address
)
247 def get_authdate(self
):
248 return self
.__get_field('authdate')
250 def set_authdate(self
, date
):
251 if not date
and 'GIT_AUTHOR_DATE' in os
.environ
:
252 date
= os
.environ
['GIT_AUTHOR_DATE']
253 self
.__set_field('authdate', date
)
255 def get_commname(self
):
256 return self
.__get_field('commname')
258 def set_commname(self
, name
):
260 if config
.has_option('stgit', 'commname'):
261 name
= config
.get('stgit', 'commname')
262 elif 'GIT_COMMITTER_NAME' in os
.environ
:
263 name
= os
.environ
['GIT_COMMITTER_NAME']
264 self
.__set_field('commname', name
)
266 def get_commemail(self
):
267 return self
.__get_field('commemail')
269 def set_commemail(self
, address
):
271 if config
.has_option('stgit', 'commemail'):
272 address
= config
.get('stgit', 'commemail')
273 elif 'GIT_COMMITTER_EMAIL' in os
.environ
:
274 address
= os
.environ
['GIT_COMMITTER_EMAIL']
275 self
.__set_field('commemail', address
)
279 """Class including the operations on series
281 def __init__(self
, name
= None):
282 """Takes a series name as the parameter.
288 self
.__name
= git
.get_head_file()
289 self
.__base_dir
= basedir
.get()
290 except git
.GitException
, ex
:
291 raise StackException
, 'GIT tree not initialised: %s' % ex
293 self
.__series_dir
= os
.path
.join(self
.__base_dir
, 'patches',
295 self
.__refs_dir
= os
.path
.join(self
.__base_dir
, 'refs', 'patches',
297 self
.__base_file
= os
.path
.join(self
.__base_dir
, 'refs', 'bases',
300 self
.__applied_file
= os
.path
.join(self
.__series_dir
, 'applied')
301 self
.__unapplied_file
= os
.path
.join(self
.__series_dir
, 'unapplied')
302 self
.__current_file
= os
.path
.join(self
.__series_dir
, 'current')
303 self
.__descr_file
= os
.path
.join(self
.__series_dir
, 'description')
305 # where this series keeps its patches
306 self
.__patch_dir
= os
.path
.join(self
.__series_dir
, 'patches')
307 if not os
.path
.isdir(self
.__patch_dir
):
308 self
.__patch_dir
= self
.__series_dir
310 # if no __refs_dir, create and populate it (upgrade old repositories)
311 if self
.is_initialised() and not os
.path
.isdir(self
.__refs_dir
):
312 os
.makedirs(self
.__refs_dir
)
313 for patch
in self
.get_applied() + self
.get_unapplied():
314 self
.get_patch(patch
).update_top_ref()
316 def get_branch(self
):
317 """Return the branch name for the Series object
321 def __set_current(self
, name
):
322 """Sets the topmost patch
325 write_string(self
.__current_file
, name
)
327 create_empty_file(self
.__current_file
)
329 def get_patch(self
, name
):
330 """Return a Patch object for the given name
332 return Patch(name
, self
.__patch_dir
, self
.__refs_dir
)
334 def get_current(self
):
335 """Return a Patch object representing the topmost patch
337 if os
.path
.isfile(self
.__current_file
):
338 name
= read_string(self
.__current_file
)
346 def get_applied(self
):
347 if not os
.path
.isfile(self
.__applied_file
):
348 raise StackException
, 'Branch "%s" not initialised' % self
.__name
349 f
= file(self
.__applied_file
)
350 names
= [line
.strip() for line
in f
.readlines()]
354 def get_unapplied(self
):
355 if not os
.path
.isfile(self
.__unapplied_file
):
356 raise StackException
, 'Branch "%s" not initialised' % self
.__name
357 f
= file(self
.__unapplied_file
)
358 names
= [line
.strip() for line
in f
.readlines()]
362 def get_base_file(self
):
363 self
.__begin_stack_check()
364 return self
.__base_file
366 def get_protected(self
):
367 return os
.path
.isfile(os
.path
.join(self
.__series_dir
, 'protected'))
370 protect_file
= os
.path
.join(self
.__series_dir
, 'protected')
371 if not os
.path
.isfile(protect_file
):
372 create_empty_file(protect_file
)
375 protect_file
= os
.path
.join(self
.__series_dir
, 'protected')
376 if os
.path
.isfile(protect_file
):
377 os
.remove(protect_file
)
379 def get_description(self
):
380 if os
.path
.isfile(self
.__descr_file
):
381 return read_string(self
.__descr_file
)
385 def __patch_is_current(self
, patch
):
386 return patch
.get_name() == read_string(self
.__current_file
)
388 def __patch_applied(self
, name
):
389 """Return true if the patch exists in the applied list
391 return name
in self
.get_applied()
393 def __patch_unapplied(self
, name
):
394 """Return true if the patch exists in the unapplied list
396 return name
in self
.get_unapplied()
398 def __begin_stack_check(self
):
399 """Save the current HEAD into .git/refs/heads/base if the stack
402 if len(self
.get_applied()) == 0:
403 head
= git
.get_head()
404 write_string(self
.__base_file
, head
)
406 def __end_stack_check(self
):
407 """Remove .git/refs/heads/base if the stack is empty.
408 This warning should never happen
410 if len(self
.get_applied()) == 0 \
411 and read_string(self
.__base_file
) != git
.get_head():
412 print 'Warning: stack empty but the HEAD and base are different'
414 def head_top_equal(self
):
415 """Return true if the head and the top are the same
417 crt
= self
.get_current()
419 # we don't care, no patches applied
421 return git
.get_head() == Patch(crt
, self
.__patch_dir
,
422 self
.__refs_dir
).get_top()
424 def is_initialised(self
):
425 """Checks if series is already initialised
427 return os
.path
.isdir(self
.__patch_dir
)
430 """Initialises the stgit series
432 bases_dir
= os
.path
.join(self
.__base_dir
, 'refs', 'bases')
434 if os
.path
.exists(self
.__patch_dir
):
435 raise StackException
, self
.__patch_dir
+ ' already exists'
436 if os
.path
.exists(self
.__refs_dir
):
437 raise StackException
, self
.__refs_dir
+ ' already exists'
438 if os
.path
.exists(self
.__base_file
):
439 raise StackException
, self
.__base_file
+ ' already exists'
441 os
.makedirs(self
.__patch_dir
)
443 if not os
.path
.isdir(bases_dir
):
444 os
.makedirs(bases_dir
)
446 create_empty_file(self
.__applied_file
)
447 create_empty_file(self
.__unapplied_file
)
448 create_empty_file(self
.__descr_file
)
449 os
.makedirs(os
.path
.join(self
.__series_dir
, 'patches'))
450 os
.makedirs(self
.__refs_dir
)
451 self
.__begin_stack_check()
454 """Either convert to use a separate patch directory, or
455 unconvert to place the patches in the same directory with
458 if self
.__patch_dir
== self
.__series_dir
:
459 print 'Converting old-style to new-style...',
462 self
.__patch_dir
= os
.path
.join(self
.__series_dir
, 'patches')
463 os
.makedirs(self
.__patch_dir
)
465 for p
in self
.get_applied() + self
.get_unapplied():
466 src
= os
.path
.join(self
.__series_dir
, p
)
467 dest
= os
.path
.join(self
.__patch_dir
, p
)
473 print 'Converting new-style to old-style...',
476 for p
in self
.get_applied() + self
.get_unapplied():
477 src
= os
.path
.join(self
.__patch_dir
, p
)
478 dest
= os
.path
.join(self
.__series_dir
, p
)
481 if not os
.listdir(self
.__patch_dir
):
482 os
.rmdir(self
.__patch_dir
)
485 print 'Patch directory %s is not empty.' % self
.__name
487 self
.__patch_dir
= self
.__series_dir
489 def rename(self
, to_name
):
492 to_stack
= Series(to_name
)
494 if to_stack
.is_initialised():
495 raise StackException
, '"%s" already exists' % to_stack
.get_branch()
496 if os
.path
.exists(to_stack
.__base_file
):
497 os
.remove(to_stack
.__base_file
)
499 git
.rename_branch(self
.__name
, to_name
)
501 if os
.path
.isdir(self
.__series_dir
):
502 os
.rename(self
.__series_dir
, to_stack
.__series_dir
)
503 if os
.path
.exists(self
.__base_file
):
504 os
.rename(self
.__base_file
, to_stack
.__base_file
)
506 self
.__init__(to_name
)
508 def clone(self
, target_series
):
511 base
= read_string(self
.get_base_file())
512 git
.create_branch(target_series
, tree_id
= base
)
513 Series(target_series
).init()
514 new_series
= Series(target_series
)
516 # generate an artificial description file
517 write_string(new_series
.__descr_file
, 'clone of "%s"' % self
.__name
)
519 # clone self's entire series as unapplied patches
520 patches
= self
.get_applied() + self
.get_unapplied()
523 patch
= self
.get_patch(p
)
524 new_series
.new_patch(p
, message
= patch
.get_description(),
525 can_edit
= False, unapplied
= True,
526 bottom
= patch
.get_bottom(),
527 top
= patch
.get_top(),
528 author_name
= patch
.get_authname(),
529 author_email
= patch
.get_authemail(),
530 author_date
= patch
.get_authdate())
532 # fast forward the cloned series to self's top
533 new_series
.forward_patches(self
.get_applied())
535 def delete(self
, force
= False):
536 """Deletes an stgit series
538 if self
.is_initialised():
539 patches
= self
.get_unapplied() + self
.get_applied()
540 if not force
and patches
:
541 raise StackException
, \
542 'Cannot delete: the series still contains patches'
544 Patch(p
, self
.__patch_dir
, self
.__refs_dir
).delete()
546 if os
.path
.exists(self
.__applied_file
):
547 os
.remove(self
.__applied_file
)
548 if os
.path
.exists(self
.__unapplied_file
):
549 os
.remove(self
.__unapplied_file
)
550 if os
.path
.exists(self
.__current_file
):
551 os
.remove(self
.__current_file
)
552 if os
.path
.exists(self
.__descr_file
):
553 os
.remove(self
.__descr_file
)
554 if not os
.listdir(self
.__patch_dir
):
555 os
.rmdir(self
.__patch_dir
)
557 print 'Patch directory %s is not empty.' % self
.__name
558 if not os
.listdir(self
.__series_dir
):
559 os
.rmdir(self
.__series_dir
)
561 print 'Series directory %s is not empty.' % self
.__name
562 if not os
.listdir(self
.__refs_dir
):
563 os
.rmdir(self
.__refs_dir
)
565 print 'Refs directory %s is not empty.' % self
.__refs_dir
567 if os
.path
.exists(self
.__base_file
):
568 os
.remove(self
.__base_file
)
570 def refresh_patch(self
, files
= None, message
= None, edit
= False,
573 author_name
= None, author_email
= None,
575 committer_name
= None, committer_email
= None,
577 """Generates a new commit for the given patch
579 name
= self
.get_current()
581 raise StackException
, 'No patches applied'
583 patch
= Patch(name
, self
.__patch_dir
, self
.__refs_dir
)
585 descr
= patch
.get_description()
586 if not (message
or descr
):
592 if not message
and edit
:
593 descr
= edit_file(self
, descr
.rstrip(), \
594 'Please edit the description for patch "%s" ' \
595 'above.' % name
, show_patch
)
598 author_name
= patch
.get_authname()
600 author_email
= patch
.get_authemail()
602 author_date
= patch
.get_authdate()
603 if not committer_name
:
604 committer_name
= patch
.get_commname()
605 if not committer_email
:
606 committer_email
= patch
.get_commemail()
608 bottom
= patch
.get_bottom()
610 commit_id
= git
.commit(files
= files
,
611 message
= descr
, parents
= [bottom
],
612 cache_update
= cache_update
,
614 author_name
= author_name
,
615 author_email
= author_email
,
616 author_date
= author_date
,
617 committer_name
= committer_name
,
618 committer_email
= committer_email
)
620 patch
.set_bottom(bottom
, backup
= backup
)
621 patch
.set_top(commit_id
, backup
= backup
)
622 patch
.set_description(descr
)
623 patch
.set_authname(author_name
)
624 patch
.set_authemail(author_email
)
625 patch
.set_authdate(author_date
)
626 patch
.set_commname(committer_name
)
627 patch
.set_commemail(committer_email
)
631 def undo_refresh(self
):
632 """Undo the patch boundaries changes caused by 'refresh'
634 name
= self
.get_current()
637 patch
= Patch(name
, self
.__patch_dir
, self
.__refs_dir
)
638 old_bottom
= patch
.get_old_bottom()
639 old_top
= patch
.get_old_top()
641 # the bottom of the patch is not changed by refresh. If the
642 # old_bottom is different, there wasn't any previous 'refresh'
643 # command (probably only a 'push')
644 if old_bottom
!= patch
.get_bottom() or old_top
== patch
.get_top():
645 raise StackException
, 'No refresh undo information available'
647 git
.reset(tree_id
= old_top
, check_out
= False)
648 patch
.restore_old_boundaries()
650 def new_patch(self
, name
, message
= None, can_edit
= True,
651 unapplied
= False, show_patch
= False,
652 top
= None, bottom
= None,
653 author_name
= None, author_email
= None, author_date
= None,
654 committer_name
= None, committer_email
= None,
655 before_existing
= False):
656 """Creates a new patch
658 if self
.__patch_applied(name
) or self
.__patch_unapplied(name
):
659 raise StackException
, 'Patch "%s" already exists' % name
661 if not message
and can_edit
:
662 descr
= edit_file(self
, None, \
663 'Please enter the description for patch "%s" ' \
664 'above.' % name
, show_patch
)
668 head
= git
.get_head()
670 self
.__begin_stack_check()
672 patch
= Patch(name
, self
.__patch_dir
, self
.__refs_dir
)
676 patch
.set_bottom(bottom
)
678 patch
.set_bottom(head
)
684 patch
.set_description(descr
)
685 patch
.set_authname(author_name
)
686 patch
.set_authemail(author_email
)
687 patch
.set_authdate(author_date
)
688 patch
.set_commname(committer_name
)
689 patch
.set_commemail(committer_email
)
692 patches
= [patch
.get_name()] + self
.get_unapplied()
694 f
= file(self
.__unapplied_file
, 'w+')
695 f
.writelines([line
+ '\n' for line
in patches
])
699 insert_string(self
.__applied_file
, patch
.get_name())
700 if not self
.get_current():
701 self
.__set_current(name
)
703 append_string(self
.__applied_file
, patch
.get_name())
704 self
.__set_current(name
)
706 def delete_patch(self
, name
):
709 patch
= Patch(name
, self
.__patch_dir
, self
.__refs_dir
)
711 if self
.__patch_is_current(patch
):
713 elif self
.__patch_applied(name
):
714 raise StackException
, 'Cannot remove an applied patch, "%s", ' \
715 'which is not current' % name
716 elif not name
in self
.get_unapplied():
717 raise StackException
, 'Unknown patch "%s"' % name
721 unapplied
= self
.get_unapplied()
722 unapplied
.remove(name
)
723 f
= file(self
.__unapplied_file
, 'w+')
724 f
.writelines([line
+ '\n' for line
in unapplied
])
726 self
.__begin_stack_check()
728 def forward_patches(self
, names
):
729 """Try to fast-forward an array of patches.
731 On return, patches in names[0:returned_value] have been pushed on the
732 stack. Apply the rest with push_patch
734 unapplied
= self
.get_unapplied()
735 self
.__begin_stack_check()
741 assert(name
in unapplied
)
743 patch
= Patch(name
, self
.__patch_dir
, self
.__refs_dir
)
746 bottom
= patch
.get_bottom()
747 top
= patch
.get_top()
749 # top != bottom always since we have a commit for each patch
751 # reset the backup information
752 patch
.set_bottom(head
, backup
= True)
753 patch
.set_top(top
, backup
= True)
756 head_tree
= git
.get_commit(head
).get_tree()
757 bottom_tree
= git
.get_commit(bottom
).get_tree()
758 if head_tree
== bottom_tree
:
759 # We must just reparent this patch and create a new commit
761 descr
= patch
.get_description()
762 author_name
= patch
.get_authname()
763 author_email
= patch
.get_authemail()
764 author_date
= patch
.get_authdate()
765 committer_name
= patch
.get_commname()
766 committer_email
= patch
.get_commemail()
768 top_tree
= git
.get_commit(top
).get_tree()
770 top
= git
.commit(message
= descr
, parents
= [head
],
771 cache_update
= False,
774 author_name
= author_name
,
775 author_email
= author_email
,
776 author_date
= author_date
,
777 committer_name
= committer_name
,
778 committer_email
= committer_email
)
780 patch
.set_bottom(head
, backup
= True)
781 patch
.set_top(top
, backup
= True)
784 # stop the fast-forwarding, must do a real merge
788 unapplied
.remove(name
)
795 append_strings(self
.__applied_file
, names
[0:forwarded
])
797 f
= file(self
.__unapplied_file
, 'w+')
798 f
.writelines([line
+ '\n' for line
in unapplied
])
801 self
.__set_current(name
)
805 def merged_patches(self
, names
):
806 """Test which patches were merged upstream by reverse-applying
807 them in reverse order. The function returns the list of
808 patches detected to have been applied. The state of the tree
809 is restored to the original one
811 patches
= [Patch(name
, self
.__patch_dir
, self
.__refs_dir
)
817 if git
.apply_diff(p
.get_top(), p
.get_bottom(), False):
818 merged
.append(p
.get_name())
825 def push_patch(self
, name
, empty
= False):
826 """Pushes a patch on the stack
828 unapplied
= self
.get_unapplied()
829 assert(name
in unapplied
)
831 self
.__begin_stack_check()
833 patch
= Patch(name
, self
.__patch_dir
, self
.__refs_dir
)
835 head
= git
.get_head()
836 bottom
= patch
.get_bottom()
837 top
= patch
.get_top()
842 # top != bottom always since we have a commit for each patch
844 # just make an empty patch (top = bottom = HEAD). This
845 # option is useful to allow undoing already merged
846 # patches. The top is updated by refresh_patch since we
847 # need an empty commit
848 patch
.set_bottom(head
, backup
= True)
849 patch
.set_top(head
, backup
= True)
852 # reset the backup information
853 patch
.set_bottom(bottom
, backup
= True)
854 patch
.set_top(top
, backup
= True)
858 # new patch needs to be refreshed.
859 # The current patch is empty after merge.
860 patch
.set_bottom(head
, backup
= True)
861 patch
.set_top(head
, backup
= True)
863 # Try the fast applying first. If this fails, fall back to the
865 if not git
.apply_diff(bottom
, top
):
866 # if git.apply_diff() fails, the patch requires a diff3
867 # merge and can be reported as modified
870 # merge can fail but the patch needs to be pushed
872 git
.merge(bottom
, head
, top
)
873 except git
.GitException
, ex
:
874 print >> sys
.stderr
, \
875 'The merge failed during "push". ' \
876 'Use "refresh" after fixing the conflicts'
878 append_string(self
.__applied_file
, name
)
880 unapplied
.remove(name
)
881 f
= file(self
.__unapplied_file
, 'w+')
882 f
.writelines([line
+ '\n' for line
in unapplied
])
885 self
.__set_current(name
)
887 # head == bottom case doesn't need to refresh the patch
888 if empty
or head
!= bottom
:
890 # if the merge was OK and no conflicts, just refresh the patch
891 # The GIT cache was already updated by the merge operation
892 self
.refresh_patch(cache_update
= False)
894 raise StackException
, str(ex
)
899 name
= self
.get_current()
902 patch
= Patch(name
, self
.__patch_dir
, self
.__refs_dir
)
903 old_bottom
= patch
.get_old_bottom()
904 old_top
= patch
.get_old_top()
906 # the top of the patch is changed by a push operation only
907 # together with the bottom (otherwise the top was probably
908 # modified by 'refresh'). If they are both unchanged, there
910 if old_bottom
== patch
.get_bottom() and old_top
!= patch
.get_top():
911 raise StackException
, 'No push undo information available'
915 return patch
.restore_old_boundaries()
917 def pop_patch(self
, name
):
918 """Pops the top patch from the stack
920 applied
= self
.get_applied()
922 assert(name
in applied
)
924 patch
= Patch(name
, self
.__patch_dir
, self
.__refs_dir
)
926 git
.switch(patch
.get_bottom())
928 # save the new applied list
929 idx
= applied
.index(name
) + 1
931 popped
= applied
[:idx
]
933 unapplied
= popped
+ self
.get_unapplied()
935 f
= file(self
.__unapplied_file
, 'w+')
936 f
.writelines([line
+ '\n' for line
in unapplied
])
942 f
= file(self
.__applied_file
, 'w+')
943 f
.writelines([line
+ '\n' for line
in applied
])
947 self
.__set_current(None)
949 self
.__set_current(applied
[-1])
951 self
.__end_stack_check()
953 def empty_patch(self
, name
):
954 """Returns True if the patch is empty
956 patch
= Patch(name
, self
.__patch_dir
, self
.__refs_dir
)
957 bottom
= patch
.get_bottom()
958 top
= patch
.get_top()
962 elif git
.get_commit(top
).get_tree() \
963 == git
.get_commit(bottom
).get_tree():
968 def rename_patch(self
, oldname
, newname
):
969 applied
= self
.get_applied()
970 unapplied
= self
.get_unapplied()
972 if oldname
== newname
:
973 raise StackException
, '"To" name and "from" name are the same'
975 if newname
in applied
or newname
in unapplied
:
976 raise StackException
, 'Patch "%s" already exists' % newname
978 if oldname
in unapplied
:
979 Patch(oldname
, self
.__patch_dir
, self
.__refs_dir
).rename(newname
)
980 unapplied
[unapplied
.index(oldname
)] = newname
982 f
= file(self
.__unapplied_file
, 'w+')
983 f
.writelines([line
+ '\n' for line
in unapplied
])
985 elif oldname
in applied
:
986 Patch(oldname
, self
.__patch_dir
, self
.__refs_dir
).rename(newname
)
987 if oldname
== self
.get_current():
988 self
.__set_current(newname
)
990 applied
[applied
.index(oldname
)] = newname
992 f
= file(self
.__applied_file
, 'w+')
993 f
.writelines([line
+ '\n' for line
in applied
])
996 raise StackException
, 'Unknown patch "%s"' % oldname