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 self
.is_initialised():
435 raise StackException
, self
.__patch_dir
+ ' already exists'
436 os
.makedirs(self
.__patch_dir
)
438 if not os
.path
.isdir(bases_dir
):
439 os
.makedirs(bases_dir
)
441 create_empty_file(self
.__applied_file
)
442 create_empty_file(self
.__unapplied_file
)
443 create_empty_file(self
.__descr_file
)
444 os
.makedirs(os
.path
.join(self
.__series_dir
, 'patches'))
445 os
.makedirs(self
.__refs_dir
)
446 self
.__begin_stack_check()
449 """Either convert to use a separate patch directory, or
450 unconvert to place the patches in the same directory with
453 if self
.__patch_dir
== self
.__series_dir
:
454 print 'Converting old-style to new-style...',
457 self
.__patch_dir
= os
.path
.join(self
.__series_dir
, 'patches')
458 os
.makedirs(self
.__patch_dir
)
460 for p
in self
.get_applied() + self
.get_unapplied():
461 src
= os
.path
.join(self
.__series_dir
, p
)
462 dest
= os
.path
.join(self
.__patch_dir
, p
)
468 print 'Converting new-style to old-style...',
471 for p
in self
.get_applied() + self
.get_unapplied():
472 src
= os
.path
.join(self
.__patch_dir
, p
)
473 dest
= os
.path
.join(self
.__series_dir
, p
)
476 if not os
.listdir(self
.__patch_dir
):
477 os
.rmdir(self
.__patch_dir
)
480 print 'Patch directory %s is not empty.' % self
.__name
482 self
.__patch_dir
= self
.__series_dir
484 def rename(self
, to_name
):
487 to_stack
= Series(to_name
)
489 if to_stack
.is_initialised():
490 raise StackException
, '"%s" already exists' % to_stack
.get_branch()
491 if os
.path
.exists(to_stack
.__base_file
):
492 os
.remove(to_stack
.__base_file
)
494 git
.rename_branch(self
.__name
, to_name
)
496 if os
.path
.isdir(self
.__series_dir
):
497 os
.rename(self
.__series_dir
, to_stack
.__series_dir
)
498 if os
.path
.exists(self
.__base_file
):
499 os
.rename(self
.__base_file
, to_stack
.__base_file
)
501 self
.__init__(to_name
)
503 def clone(self
, target_series
):
506 base
= read_string(self
.get_base_file())
507 git
.create_branch(target_series
, tree_id
= base
)
508 Series(target_series
).init()
509 new_series
= Series(target_series
)
511 # generate an artificial description file
512 write_string(new_series
.__descr_file
, 'clone of "%s"' % self
.__name
)
514 # clone self's entire series as unapplied patches
515 patches
= self
.get_applied() + self
.get_unapplied()
518 patch
= self
.get_patch(p
)
519 new_series
.new_patch(p
, message
= patch
.get_description(),
520 can_edit
= False, unapplied
= True,
521 bottom
= patch
.get_bottom(),
522 top
= patch
.get_top(),
523 author_name
= patch
.get_authname(),
524 author_email
= patch
.get_authemail(),
525 author_date
= patch
.get_authdate())
527 # fast forward the cloned series to self's top
528 new_series
.forward_patches(self
.get_applied())
530 def delete(self
, force
= False):
531 """Deletes an stgit series
533 if self
.is_initialised():
534 patches
= self
.get_unapplied() + self
.get_applied()
535 if not force
and patches
:
536 raise StackException
, \
537 'Cannot delete: the series still contains patches'
539 Patch(p
, self
.__patch_dir
, self
.__refs_dir
).delete()
541 if os
.path
.exists(self
.__applied_file
):
542 os
.remove(self
.__applied_file
)
543 if os
.path
.exists(self
.__unapplied_file
):
544 os
.remove(self
.__unapplied_file
)
545 if os
.path
.exists(self
.__current_file
):
546 os
.remove(self
.__current_file
)
547 if os
.path
.exists(self
.__descr_file
):
548 os
.remove(self
.__descr_file
)
549 if not os
.listdir(self
.__patch_dir
):
550 os
.rmdir(self
.__patch_dir
)
552 print 'Patch directory %s is not empty.' % self
.__name
553 if not os
.listdir(self
.__series_dir
):
554 os
.rmdir(self
.__series_dir
)
556 print 'Series directory %s is not empty.' % self
.__name
557 if not os
.listdir(self
.__refs_dir
):
558 os
.rmdir(self
.__refs_dir
)
560 print 'Refs directory %s is not empty.' % self
.__refs_dir
562 if os
.path
.exists(self
.__base_file
):
563 os
.remove(self
.__base_file
)
565 def refresh_patch(self
, files
= None, message
= None, edit
= False,
568 author_name
= None, author_email
= None,
570 committer_name
= None, committer_email
= None,
572 """Generates a new commit for the given patch
574 name
= self
.get_current()
576 raise StackException
, 'No patches applied'
578 patch
= Patch(name
, self
.__patch_dir
, self
.__refs_dir
)
580 descr
= patch
.get_description()
581 if not (message
or descr
):
587 if not message
and edit
:
588 descr
= edit_file(self
, descr
.rstrip(), \
589 'Please edit the description for patch "%s" ' \
590 'above.' % name
, show_patch
)
593 author_name
= patch
.get_authname()
595 author_email
= patch
.get_authemail()
597 author_date
= patch
.get_authdate()
598 if not committer_name
:
599 committer_name
= patch
.get_commname()
600 if not committer_email
:
601 committer_email
= patch
.get_commemail()
603 bottom
= patch
.get_bottom()
605 commit_id
= git
.commit(files
= files
,
606 message
= descr
, parents
= [bottom
],
607 cache_update
= cache_update
,
609 author_name
= author_name
,
610 author_email
= author_email
,
611 author_date
= author_date
,
612 committer_name
= committer_name
,
613 committer_email
= committer_email
)
615 patch
.set_bottom(bottom
, backup
= backup
)
616 patch
.set_top(commit_id
, backup
= backup
)
617 patch
.set_description(descr
)
618 patch
.set_authname(author_name
)
619 patch
.set_authemail(author_email
)
620 patch
.set_authdate(author_date
)
621 patch
.set_commname(committer_name
)
622 patch
.set_commemail(committer_email
)
626 def undo_refresh(self
):
627 """Undo the patch boundaries changes caused by 'refresh'
629 name
= self
.get_current()
632 patch
= Patch(name
, self
.__patch_dir
, self
.__refs_dir
)
633 old_bottom
= patch
.get_old_bottom()
634 old_top
= patch
.get_old_top()
636 # the bottom of the patch is not changed by refresh. If the
637 # old_bottom is different, there wasn't any previous 'refresh'
638 # command (probably only a 'push')
639 if old_bottom
!= patch
.get_bottom() or old_top
== patch
.get_top():
640 raise StackException
, 'No refresh undo information available'
642 git
.reset(tree_id
= old_top
, check_out
= False)
643 patch
.restore_old_boundaries()
645 def new_patch(self
, name
, message
= None, can_edit
= True,
646 unapplied
= False, show_patch
= False,
647 top
= None, bottom
= None,
648 author_name
= None, author_email
= None, author_date
= None,
649 committer_name
= None, committer_email
= None,
650 before_existing
= False):
651 """Creates a new patch
653 if self
.__patch_applied(name
) or self
.__patch_unapplied(name
):
654 raise StackException
, 'Patch "%s" already exists' % name
656 if not message
and can_edit
:
657 descr
= edit_file(self
, None, \
658 'Please enter the description for patch "%s" ' \
659 'above.' % name
, show_patch
)
663 head
= git
.get_head()
665 self
.__begin_stack_check()
667 patch
= Patch(name
, self
.__patch_dir
, self
.__refs_dir
)
671 patch
.set_bottom(bottom
)
673 patch
.set_bottom(head
)
679 patch
.set_description(descr
)
680 patch
.set_authname(author_name
)
681 patch
.set_authemail(author_email
)
682 patch
.set_authdate(author_date
)
683 patch
.set_commname(committer_name
)
684 patch
.set_commemail(committer_email
)
687 patches
= [patch
.get_name()] + self
.get_unapplied()
689 f
= file(self
.__unapplied_file
, 'w+')
690 f
.writelines([line
+ '\n' for line
in patches
])
694 insert_string(self
.__applied_file
, patch
.get_name())
695 if not self
.get_current():
696 self
.__set_current(name
)
698 append_string(self
.__applied_file
, patch
.get_name())
699 self
.__set_current(name
)
701 def delete_patch(self
, name
):
704 patch
= Patch(name
, self
.__patch_dir
, self
.__refs_dir
)
706 if self
.__patch_is_current(patch
):
708 elif self
.__patch_applied(name
):
709 raise StackException
, 'Cannot remove an applied patch, "%s", ' \
710 'which is not current' % name
711 elif not name
in self
.get_unapplied():
712 raise StackException
, 'Unknown patch "%s"' % name
716 unapplied
= self
.get_unapplied()
717 unapplied
.remove(name
)
718 f
= file(self
.__unapplied_file
, 'w+')
719 f
.writelines([line
+ '\n' for line
in unapplied
])
721 self
.__begin_stack_check()
723 def forward_patches(self
, names
):
724 """Try to fast-forward an array of patches.
726 On return, patches in names[0:returned_value] have been pushed on the
727 stack. Apply the rest with push_patch
729 unapplied
= self
.get_unapplied()
730 self
.__begin_stack_check()
736 assert(name
in unapplied
)
738 patch
= Patch(name
, self
.__patch_dir
, self
.__refs_dir
)
741 bottom
= patch
.get_bottom()
742 top
= patch
.get_top()
744 # top != bottom always since we have a commit for each patch
746 # reset the backup information
747 patch
.set_bottom(head
, backup
= True)
748 patch
.set_top(top
, backup
= True)
751 head_tree
= git
.get_commit(head
).get_tree()
752 bottom_tree
= git
.get_commit(bottom
).get_tree()
753 if head_tree
== bottom_tree
:
754 # We must just reparent this patch and create a new commit
756 descr
= patch
.get_description()
757 author_name
= patch
.get_authname()
758 author_email
= patch
.get_authemail()
759 author_date
= patch
.get_authdate()
760 committer_name
= patch
.get_commname()
761 committer_email
= patch
.get_commemail()
763 top_tree
= git
.get_commit(top
).get_tree()
765 top
= git
.commit(message
= descr
, parents
= [head
],
766 cache_update
= False,
769 author_name
= author_name
,
770 author_email
= author_email
,
771 author_date
= author_date
,
772 committer_name
= committer_name
,
773 committer_email
= committer_email
)
775 patch
.set_bottom(head
, backup
= True)
776 patch
.set_top(top
, backup
= True)
779 # stop the fast-forwarding, must do a real merge
783 unapplied
.remove(name
)
790 append_strings(self
.__applied_file
, names
[0:forwarded
])
792 f
= file(self
.__unapplied_file
, 'w+')
793 f
.writelines([line
+ '\n' for line
in unapplied
])
796 self
.__set_current(name
)
800 def merged_patches(self
, names
):
801 """Test which patches were merged upstream by reverse-applying
802 them in reverse order. The function returns the list of
803 patches detected to have been applied. The state of the tree
804 is restored to the original one
806 patches
= [Patch(name
, self
.__patch_dir
, self
.__refs_dir
)
812 if git
.apply_diff(p
.get_top(), p
.get_bottom(), False):
813 merged
.append(p
.get_name())
820 def push_patch(self
, name
, empty
= False):
821 """Pushes a patch on the stack
823 unapplied
= self
.get_unapplied()
824 assert(name
in unapplied
)
826 self
.__begin_stack_check()
828 patch
= Patch(name
, self
.__patch_dir
, self
.__refs_dir
)
830 head
= git
.get_head()
831 bottom
= patch
.get_bottom()
832 top
= patch
.get_top()
837 # top != bottom always since we have a commit for each patch
839 # just make an empty patch (top = bottom = HEAD). This
840 # option is useful to allow undoing already merged
841 # patches. The top is updated by refresh_patch since we
842 # need an empty commit
843 patch
.set_bottom(head
, backup
= True)
844 patch
.set_top(head
, backup
= True)
847 # reset the backup information
848 patch
.set_bottom(bottom
, backup
= True)
849 patch
.set_top(top
, backup
= True)
853 # new patch needs to be refreshed.
854 # The current patch is empty after merge.
855 patch
.set_bottom(head
, backup
= True)
856 patch
.set_top(head
, backup
= True)
858 # Try the fast applying first. If this fails, fall back to the
860 if not git
.apply_diff(bottom
, top
):
861 # if git.apply_diff() fails, the patch requires a diff3
862 # merge and can be reported as modified
865 # merge can fail but the patch needs to be pushed
867 git
.merge(bottom
, head
, top
)
868 except git
.GitException
, ex
:
869 print >> sys
.stderr
, \
870 'The merge failed during "push". ' \
871 'Use "refresh" after fixing the conflicts'
873 append_string(self
.__applied_file
, name
)
875 unapplied
.remove(name
)
876 f
= file(self
.__unapplied_file
, 'w+')
877 f
.writelines([line
+ '\n' for line
in unapplied
])
880 self
.__set_current(name
)
882 # head == bottom case doesn't need to refresh the patch
883 if empty
or head
!= bottom
:
885 # if the merge was OK and no conflicts, just refresh the patch
886 # The GIT cache was already updated by the merge operation
887 self
.refresh_patch(cache_update
= False)
889 raise StackException
, str(ex
)
894 name
= self
.get_current()
897 patch
= Patch(name
, self
.__patch_dir
, self
.__refs_dir
)
898 old_bottom
= patch
.get_old_bottom()
899 old_top
= patch
.get_old_top()
901 # the top of the patch is changed by a push operation only
902 # together with the bottom (otherwise the top was probably
903 # modified by 'refresh'). If they are both unchanged, there
905 if old_bottom
== patch
.get_bottom() and old_top
!= patch
.get_top():
906 raise StackException
, 'No push undo information available'
910 return patch
.restore_old_boundaries()
912 def pop_patch(self
, name
):
913 """Pops the top patch from the stack
915 applied
= self
.get_applied()
917 assert(name
in applied
)
919 patch
= Patch(name
, self
.__patch_dir
, self
.__refs_dir
)
921 git
.switch(patch
.get_bottom())
923 # save the new applied list
924 idx
= applied
.index(name
) + 1
926 popped
= applied
[:idx
]
928 unapplied
= popped
+ self
.get_unapplied()
930 f
= file(self
.__unapplied_file
, 'w+')
931 f
.writelines([line
+ '\n' for line
in unapplied
])
937 f
= file(self
.__applied_file
, 'w+')
938 f
.writelines([line
+ '\n' for line
in applied
])
942 self
.__set_current(None)
944 self
.__set_current(applied
[-1])
946 self
.__end_stack_check()
948 def empty_patch(self
, name
):
949 """Returns True if the patch is empty
951 patch
= Patch(name
, self
.__patch_dir
, self
.__refs_dir
)
952 bottom
= patch
.get_bottom()
953 top
= patch
.get_top()
957 elif git
.get_commit(top
).get_tree() \
958 == git
.get_commit(bottom
).get_tree():
963 def rename_patch(self
, oldname
, newname
):
964 applied
= self
.get_applied()
965 unapplied
= self
.get_unapplied()
967 if oldname
== newname
:
968 raise StackException
, '"To" name and "from" name are the same'
970 if newname
in applied
or newname
in unapplied
:
971 raise StackException
, 'Patch "%s" already exists' % newname
973 if oldname
in unapplied
:
974 Patch(oldname
, self
.__patch_dir
, self
.__refs_dir
).rename(newname
)
975 unapplied
[unapplied
.index(oldname
)] = newname
977 f
= file(self
.__unapplied_file
, 'w+')
978 f
.writelines([line
+ '\n' for line
in unapplied
])
980 elif oldname
in applied
:
981 Patch(oldname
, self
.__patch_dir
, self
.__refs_dir
).rename(newname
)
982 if oldname
== self
.get_current():
983 self
.__set_current(newname
)
985 applied
[applied
.index(oldname
)] = newname
987 f
= file(self
.__applied_file
, 'w+')
988 f
.writelines([line
+ '\n' for line
in applied
])
991 raise StackException
, 'Unknown patch "%s"' % oldname