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
)
429 def init(self
, create_at
=False):
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 if (create_at
!=False):
442 git
.create_branch(self
.__name
, create_at
)
444 os
.makedirs(self
.__patch
_dir
)
446 if not os
.path
.isdir(bases_dir
):
447 os
.makedirs(bases_dir
)
449 create_empty_file(self
.__applied
_file
)
450 create_empty_file(self
.__unapplied
_file
)
451 create_empty_file(self
.__descr
_file
)
452 os
.makedirs(os
.path
.join(self
.__series
_dir
, 'patches'))
453 os
.makedirs(self
.__refs
_dir
)
454 self
.__begin
_stack
_check
()
457 """Either convert to use a separate patch directory, or
458 unconvert to place the patches in the same directory with
461 if self
.__patch
_dir
== self
.__series
_dir
:
462 print 'Converting old-style to new-style...',
465 self
.__patch
_dir
= os
.path
.join(self
.__series
_dir
, 'patches')
466 os
.makedirs(self
.__patch
_dir
)
468 for p
in self
.get_applied() + self
.get_unapplied():
469 src
= os
.path
.join(self
.__series
_dir
, p
)
470 dest
= os
.path
.join(self
.__patch
_dir
, p
)
476 print 'Converting new-style to old-style...',
479 for p
in self
.get_applied() + self
.get_unapplied():
480 src
= os
.path
.join(self
.__patch
_dir
, p
)
481 dest
= os
.path
.join(self
.__series
_dir
, p
)
484 if not os
.listdir(self
.__patch
_dir
):
485 os
.rmdir(self
.__patch
_dir
)
488 print 'Patch directory %s is not empty.' % self
.__name
490 self
.__patch
_dir
= self
.__series
_dir
492 def rename(self
, to_name
):
495 to_stack
= Series(to_name
)
497 if to_stack
.is_initialised():
498 raise StackException
, '"%s" already exists' % to_stack
.get_branch()
499 if os
.path
.exists(to_stack
.__base
_file
):
500 os
.remove(to_stack
.__base
_file
)
502 git
.rename_branch(self
.__name
, to_name
)
504 if os
.path
.isdir(self
.__series
_dir
):
505 os
.rename(self
.__series
_dir
, to_stack
.__series
_dir
)
506 if os
.path
.exists(self
.__base
_file
):
507 os
.rename(self
.__base
_file
, to_stack
.__base
_file
)
508 if os
.path
.exists(self
.__refs
_dir
):
509 os
.rename(self
.__refs
_dir
, to_stack
.__refs
_dir
)
511 self
.__init
__(to_name
)
513 def clone(self
, target_series
):
516 base
= read_string(self
.get_base_file())
517 Series(target_series
).init(create_at
= base
)
518 new_series
= Series(target_series
)
520 # generate an artificial description file
521 write_string(new_series
.__descr
_file
, 'clone of "%s"' % self
.__name
)
523 # clone self's entire series as unapplied patches
524 patches
= self
.get_applied() + self
.get_unapplied()
527 patch
= self
.get_patch(p
)
528 new_series
.new_patch(p
, message
= patch
.get_description(),
529 can_edit
= False, unapplied
= True,
530 bottom
= patch
.get_bottom(),
531 top
= patch
.get_top(),
532 author_name
= patch
.get_authname(),
533 author_email
= patch
.get_authemail(),
534 author_date
= patch
.get_authdate())
536 # fast forward the cloned series to self's top
537 new_series
.forward_patches(self
.get_applied())
539 def delete(self
, force
= False):
540 """Deletes an stgit series
542 if self
.is_initialised():
543 patches
= self
.get_unapplied() + self
.get_applied()
544 if not force
and patches
:
545 raise StackException
, \
546 'Cannot delete: the series still contains patches'
548 Patch(p
, self
.__patch
_dir
, self
.__refs
_dir
).delete()
550 if os
.path
.exists(self
.__applied
_file
):
551 os
.remove(self
.__applied
_file
)
552 if os
.path
.exists(self
.__unapplied
_file
):
553 os
.remove(self
.__unapplied
_file
)
554 if os
.path
.exists(self
.__current
_file
):
555 os
.remove(self
.__current
_file
)
556 if os
.path
.exists(self
.__descr
_file
):
557 os
.remove(self
.__descr
_file
)
558 if not os
.listdir(self
.__patch
_dir
):
559 os
.rmdir(self
.__patch
_dir
)
561 print 'Patch directory %s is not empty.' % self
.__name
562 if not os
.listdir(self
.__series
_dir
):
563 os
.rmdir(self
.__series
_dir
)
565 print 'Series directory %s is not empty.' % self
.__name
566 if not os
.listdir(self
.__refs
_dir
):
567 os
.rmdir(self
.__refs
_dir
)
569 print 'Refs directory %s is not empty.' % self
.__refs
_dir
571 if os
.path
.exists(self
.__base
_file
):
572 os
.remove(self
.__base
_file
)
574 def refresh_patch(self
, files
= None, message
= None, edit
= False,
577 author_name
= None, author_email
= None,
579 committer_name
= None, committer_email
= None,
581 """Generates a new commit for the given patch
583 name
= self
.get_current()
585 raise StackException
, 'No patches applied'
587 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
589 descr
= patch
.get_description()
590 if not (message
or descr
):
596 if not message
and edit
:
597 descr
= edit_file(self
, descr
.rstrip(), \
598 'Please edit the description for patch "%s" ' \
599 'above.' % name
, show_patch
)
602 author_name
= patch
.get_authname()
604 author_email
= patch
.get_authemail()
606 author_date
= patch
.get_authdate()
607 if not committer_name
:
608 committer_name
= patch
.get_commname()
609 if not committer_email
:
610 committer_email
= patch
.get_commemail()
612 bottom
= patch
.get_bottom()
614 commit_id
= git
.commit(files
= files
,
615 message
= descr
, parents
= [bottom
],
616 cache_update
= cache_update
,
618 author_name
= author_name
,
619 author_email
= author_email
,
620 author_date
= author_date
,
621 committer_name
= committer_name
,
622 committer_email
= committer_email
)
624 patch
.set_bottom(bottom
, backup
= backup
)
625 patch
.set_top(commit_id
, backup
= backup
)
626 patch
.set_description(descr
)
627 patch
.set_authname(author_name
)
628 patch
.set_authemail(author_email
)
629 patch
.set_authdate(author_date
)
630 patch
.set_commname(committer_name
)
631 patch
.set_commemail(committer_email
)
635 def undo_refresh(self
):
636 """Undo the patch boundaries changes caused by 'refresh'
638 name
= self
.get_current()
641 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
642 old_bottom
= patch
.get_old_bottom()
643 old_top
= patch
.get_old_top()
645 # the bottom of the patch is not changed by refresh. If the
646 # old_bottom is different, there wasn't any previous 'refresh'
647 # command (probably only a 'push')
648 if old_bottom
!= patch
.get_bottom() or old_top
== patch
.get_top():
649 raise StackException
, 'No refresh undo information available'
651 git
.reset(tree_id
= old_top
, check_out
= False)
652 patch
.restore_old_boundaries()
654 def new_patch(self
, name
, message
= None, can_edit
= True,
655 unapplied
= False, show_patch
= False,
656 top
= None, bottom
= None,
657 author_name
= None, author_email
= None, author_date
= None,
658 committer_name
= None, committer_email
= None,
659 before_existing
= False):
660 """Creates a new patch
662 if self
.__patch
_applied
(name
) or self
.__patch
_unapplied
(name
):
663 raise StackException
, 'Patch "%s" already exists' % name
665 if not message
and can_edit
:
666 descr
= edit_file(self
, None, \
667 'Please enter the description for patch "%s" ' \
668 'above.' % name
, show_patch
)
672 head
= git
.get_head()
674 self
.__begin
_stack
_check
()
676 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
680 patch
.set_bottom(bottom
)
682 patch
.set_bottom(head
)
688 patch
.set_description(descr
)
689 patch
.set_authname(author_name
)
690 patch
.set_authemail(author_email
)
691 patch
.set_authdate(author_date
)
692 patch
.set_commname(committer_name
)
693 patch
.set_commemail(committer_email
)
696 patches
= [patch
.get_name()] + self
.get_unapplied()
698 f
= file(self
.__unapplied
_file
, 'w+')
699 f
.writelines([line
+ '\n' for line
in patches
])
703 insert_string(self
.__applied
_file
, patch
.get_name())
704 if not self
.get_current():
705 self
.__set
_current
(name
)
707 append_string(self
.__applied
_file
, patch
.get_name())
708 self
.__set
_current
(name
)
710 def delete_patch(self
, name
):
713 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
715 if self
.__patch
_is
_current
(patch
):
717 elif self
.__patch
_applied
(name
):
718 raise StackException
, 'Cannot remove an applied patch, "%s", ' \
719 'which is not current' % name
720 elif not name
in self
.get_unapplied():
721 raise StackException
, 'Unknown patch "%s"' % name
725 unapplied
= self
.get_unapplied()
726 unapplied
.remove(name
)
727 f
= file(self
.__unapplied
_file
, 'w+')
728 f
.writelines([line
+ '\n' for line
in unapplied
])
730 self
.__begin
_stack
_check
()
732 def forward_patches(self
, names
):
733 """Try to fast-forward an array of patches.
735 On return, patches in names[0:returned_value] have been pushed on the
736 stack. Apply the rest with push_patch
738 unapplied
= self
.get_unapplied()
739 self
.__begin
_stack
_check
()
745 assert(name
in unapplied
)
747 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
750 bottom
= patch
.get_bottom()
751 top
= patch
.get_top()
753 # top != bottom always since we have a commit for each patch
755 # reset the backup information
756 patch
.set_bottom(head
, backup
= True)
757 patch
.set_top(top
, backup
= True)
760 head_tree
= git
.get_commit(head
).get_tree()
761 bottom_tree
= git
.get_commit(bottom
).get_tree()
762 if head_tree
== bottom_tree
:
763 # We must just reparent this patch and create a new commit
765 descr
= patch
.get_description()
766 author_name
= patch
.get_authname()
767 author_email
= patch
.get_authemail()
768 author_date
= patch
.get_authdate()
769 committer_name
= patch
.get_commname()
770 committer_email
= patch
.get_commemail()
772 top_tree
= git
.get_commit(top
).get_tree()
774 top
= git
.commit(message
= descr
, parents
= [head
],
775 cache_update
= False,
778 author_name
= author_name
,
779 author_email
= author_email
,
780 author_date
= author_date
,
781 committer_name
= committer_name
,
782 committer_email
= committer_email
)
784 patch
.set_bottom(head
, backup
= True)
785 patch
.set_top(top
, backup
= True)
788 # stop the fast-forwarding, must do a real merge
792 unapplied
.remove(name
)
799 append_strings(self
.__applied
_file
, names
[0:forwarded
])
801 f
= file(self
.__unapplied
_file
, 'w+')
802 f
.writelines([line
+ '\n' for line
in unapplied
])
805 self
.__set
_current
(name
)
809 def merged_patches(self
, names
):
810 """Test which patches were merged upstream by reverse-applying
811 them in reverse order. The function returns the list of
812 patches detected to have been applied. The state of the tree
813 is restored to the original one
815 patches
= [Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
821 if git
.apply_diff(p
.get_top(), p
.get_bottom(), False):
822 merged
.append(p
.get_name())
829 def push_patch(self
, name
, empty
= False):
830 """Pushes a patch on the stack
832 unapplied
= self
.get_unapplied()
833 assert(name
in unapplied
)
835 self
.__begin
_stack
_check
()
837 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
839 head
= git
.get_head()
840 bottom
= patch
.get_bottom()
841 top
= patch
.get_top()
846 # top != bottom always since we have a commit for each patch
848 # just make an empty patch (top = bottom = HEAD). This
849 # option is useful to allow undoing already merged
850 # patches. The top is updated by refresh_patch since we
851 # need an empty commit
852 patch
.set_bottom(head
, backup
= True)
853 patch
.set_top(head
, backup
= True)
856 # reset the backup information
857 patch
.set_bottom(bottom
, backup
= True)
858 patch
.set_top(top
, backup
= True)
862 # new patch needs to be refreshed.
863 # The current patch is empty after merge.
864 patch
.set_bottom(head
, backup
= True)
865 patch
.set_top(head
, backup
= True)
867 # Try the fast applying first. If this fails, fall back to the
869 if not git
.apply_diff(bottom
, top
):
870 # if git.apply_diff() fails, the patch requires a diff3
871 # merge and can be reported as modified
874 # merge can fail but the patch needs to be pushed
876 git
.merge(bottom
, head
, top
)
877 except git
.GitException
, ex
:
878 print >> sys
.stderr
, \
879 'The merge failed during "push". ' \
880 'Use "refresh" after fixing the conflicts'
882 append_string(self
.__applied
_file
, name
)
884 unapplied
.remove(name
)
885 f
= file(self
.__unapplied
_file
, 'w+')
886 f
.writelines([line
+ '\n' for line
in unapplied
])
889 self
.__set
_current
(name
)
891 # head == bottom case doesn't need to refresh the patch
892 if empty
or head
!= bottom
:
894 # if the merge was OK and no conflicts, just refresh the patch
895 # The GIT cache was already updated by the merge operation
896 self
.refresh_patch(cache_update
= False)
898 raise StackException
, str(ex
)
903 name
= self
.get_current()
906 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
907 old_bottom
= patch
.get_old_bottom()
908 old_top
= patch
.get_old_top()
910 # the top of the patch is changed by a push operation only
911 # together with the bottom (otherwise the top was probably
912 # modified by 'refresh'). If they are both unchanged, there
914 if old_bottom
== patch
.get_bottom() and old_top
!= patch
.get_top():
915 raise StackException
, 'No push undo information available'
919 return patch
.restore_old_boundaries()
921 def pop_patch(self
, name
):
922 """Pops the top patch from the stack
924 applied
= self
.get_applied()
926 assert(name
in applied
)
928 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
930 git
.switch(patch
.get_bottom())
932 # save the new applied list
933 idx
= applied
.index(name
) + 1
935 popped
= applied
[:idx
]
937 unapplied
= popped
+ self
.get_unapplied()
939 f
= file(self
.__unapplied
_file
, 'w+')
940 f
.writelines([line
+ '\n' for line
in unapplied
])
946 f
= file(self
.__applied
_file
, 'w+')
947 f
.writelines([line
+ '\n' for line
in applied
])
951 self
.__set
_current
(None)
953 self
.__set
_current
(applied
[-1])
955 self
.__end
_stack
_check
()
957 def empty_patch(self
, name
):
958 """Returns True if the patch is empty
960 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
961 bottom
= patch
.get_bottom()
962 top
= patch
.get_top()
966 elif git
.get_commit(top
).get_tree() \
967 == git
.get_commit(bottom
).get_tree():
972 def rename_patch(self
, oldname
, newname
):
973 applied
= self
.get_applied()
974 unapplied
= self
.get_unapplied()
976 if oldname
== newname
:
977 raise StackException
, '"To" name and "from" name are the same'
979 if newname
in applied
or newname
in unapplied
:
980 raise StackException
, 'Patch "%s" already exists' % newname
982 if oldname
in unapplied
:
983 Patch(oldname
, self
.__patch
_dir
, self
.__refs
_dir
).rename(newname
)
984 unapplied
[unapplied
.index(oldname
)] = newname
986 f
= file(self
.__unapplied
_file
, 'w+')
987 f
.writelines([line
+ '\n' for line
in unapplied
])
989 elif oldname
in applied
:
990 Patch(oldname
, self
.__patch
_dir
, self
.__refs
_dir
).rename(newname
)
991 if oldname
== self
.get_current():
992 self
.__set
_current
(newname
)
994 applied
[applied
.index(oldname
)] = newname
996 f
= file(self
.__applied
_file
, 'w+')
997 f
.writelines([line
+ '\n' for line
in applied
])
1000 raise StackException
, 'Unknown patch "%s"' % oldname