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 """An object with stgit-like properties stored as files in a directory
125 def _set_dir(self
, dir):
130 def create_empty_field(self
, name
):
131 create_empty_file(os
.path
.join(self
.__dir
, name
))
133 def _get_field(self
, name
, multiline
= False):
134 id_file
= os
.path
.join(self
.__dir
, name
)
135 if os
.path
.isfile(id_file
):
136 line
= read_string(id_file
, multiline
)
144 def _set_field(self
, name
, value
, multiline
= False):
145 fname
= os
.path
.join(self
.__dir
, name
)
146 if value
and value
!= '':
147 write_string(fname
, value
, multiline
)
148 elif os
.path
.isfile(fname
):
152 class Patch(StgitObject
):
153 """Basic patch implementation
155 def __init__(self
, name
, series_dir
, refs_dir
):
156 self
.__series
_dir
= series_dir
158 self
._set
_dir
(os
.path
.join(self
.__series
_dir
, self
.__name
))
159 self
.__refs
_dir
= refs_dir
160 self
.__top
_ref
_file
= os
.path
.join(self
.__refs
_dir
, self
.__name
)
161 self
.__log
_ref
_file
= os
.path
.join(self
.__refs
_dir
,
162 self
.__name
+ '.log')
165 os
.mkdir(self
._dir
())
166 self
.create_empty_field('bottom')
167 self
.create_empty_field('top')
170 for f
in os
.listdir(self
._dir
()):
171 os
.remove(os
.path
.join(self
._dir
(), f
))
172 os
.rmdir(self
._dir
())
173 os
.remove(self
.__top
_ref
_file
)
174 if os
.path
.exists(self
.__log
_ref
_file
):
175 os
.remove(self
.__log
_ref
_file
)
180 def rename(self
, newname
):
182 old_top_ref_file
= self
.__top
_ref
_file
183 old_log_ref_file
= self
.__log
_ref
_file
184 self
.__name
= newname
185 self
._set
_dir
(os
.path
.join(self
.__series
_dir
, self
.__name
))
186 self
.__top
_ref
_file
= os
.path
.join(self
.__refs
_dir
, self
.__name
)
187 self
.__log
_ref
_file
= os
.path
.join(self
.__refs
_dir
,
188 self
.__name
+ '.log')
190 os
.rename(olddir
, self
._dir
())
191 os
.rename(old_top_ref_file
, self
.__top
_ref
_file
)
192 if os
.path
.exists(old_log_ref_file
):
193 os
.rename(old_log_ref_file
, self
.__log
_ref
_file
)
195 def __update_top_ref(self
, ref
):
196 write_string(self
.__top
_ref
_file
, ref
)
198 def __update_log_ref(self
, ref
):
199 write_string(self
.__log
_ref
_file
, ref
)
201 def update_top_ref(self
):
204 self
.__update
_top
_ref
(top
)
206 def get_old_bottom(self
):
207 return self
._get
_field
('bottom.old')
209 def get_bottom(self
):
210 return self
._get
_field
('bottom')
212 def set_bottom(self
, value
, backup
= False):
214 curr
= self
._get
_field
('bottom')
215 self
._set
_field
('bottom.old', curr
)
216 self
._set
_field
('bottom', value
)
218 def get_old_top(self
):
219 return self
._get
_field
('top.old')
222 return self
._get
_field
('top')
224 def set_top(self
, value
, backup
= False):
226 curr
= self
._get
_field
('top')
227 self
._set
_field
('top.old', curr
)
228 self
._set
_field
('top', value
)
229 self
.__update
_top
_ref
(value
)
231 def restore_old_boundaries(self
):
232 bottom
= self
._get
_field
('bottom.old')
233 top
= self
._get
_field
('top.old')
236 self
._set
_field
('bottom', bottom
)
237 self
._set
_field
('top', top
)
238 self
.__update
_top
_ref
(top
)
243 def get_description(self
):
244 return self
._get
_field
('description', True)
246 def set_description(self
, line
):
247 self
._set
_field
('description', line
, True)
249 def get_authname(self
):
250 return self
._get
_field
('authname')
252 def set_authname(self
, name
):
253 self
._set
_field
('authname', name
or git
.author().name
)
255 def get_authemail(self
):
256 return self
._get
_field
('authemail')
258 def set_authemail(self
, email
):
259 self
._set
_field
('authemail', email
or git
.author().email
)
261 def get_authdate(self
):
262 return self
._get
_field
('authdate')
264 def set_authdate(self
, date
):
265 self
._set
_field
('authdate', date
or git
.author().date
)
267 def get_commname(self
):
268 return self
._get
_field
('commname')
270 def set_commname(self
, name
):
271 self
._set
_field
('commname', name
or git
.committer().name
)
273 def get_commemail(self
):
274 return self
._get
_field
('commemail')
276 def set_commemail(self
, email
):
277 self
._set
_field
('commemail', email
or git
.committer().email
)
280 return self
._get
_field
('log')
282 def set_log(self
, value
, backup
= False):
283 self
._set
_field
('log', value
)
284 self
.__update
_log
_ref
(value
)
287 class Series(StgitObject
):
288 """Class including the operations on series
290 def __init__(self
, name
= None):
291 """Takes a series name as the parameter.
297 self
.__name
= git
.get_head_file()
298 self
.__base
_dir
= basedir
.get()
299 except git
.GitException
, ex
:
300 raise StackException
, 'GIT tree not initialised: %s' % ex
302 self
._set
_dir
(os
.path
.join(self
.__base
_dir
, 'patches', self
.__name
))
303 self
.__refs
_dir
= os
.path
.join(self
.__base
_dir
, 'refs', 'patches',
305 self
.__base
_file
= os
.path
.join(self
.__base
_dir
, 'refs', 'bases',
308 self
.__applied
_file
= os
.path
.join(self
._dir
(), 'applied')
309 self
.__unapplied
_file
= os
.path
.join(self
._dir
(), 'unapplied')
310 self
.__hidden
_file
= os
.path
.join(self
._dir
(), 'hidden')
311 self
.__current
_file
= os
.path
.join(self
._dir
(), 'current')
312 self
.__descr
_file
= os
.path
.join(self
._dir
(), 'description')
314 # where this series keeps its patches
315 self
.__patch
_dir
= os
.path
.join(self
._dir
(), 'patches')
316 if not os
.path
.isdir(self
.__patch
_dir
):
317 self
.__patch
_dir
= self
._dir
()
319 # if no __refs_dir, create and populate it (upgrade old repositories)
320 if self
.is_initialised() and not os
.path
.isdir(self
.__refs
_dir
):
321 os
.makedirs(self
.__refs
_dir
)
322 for patch
in self
.get_applied() + self
.get_unapplied():
323 self
.get_patch(patch
).update_top_ref()
326 self
.__trash
_dir
= os
.path
.join(self
._dir
(), 'trash')
327 if self
.is_initialised() and not os
.path
.isdir(self
.__trash
_dir
):
328 os
.makedirs(self
.__trash
_dir
)
330 def get_branch(self
):
331 """Return the branch name for the Series object
335 def __set_current(self
, name
):
336 """Sets the topmost patch
338 self
._set
_field
('current', name
)
340 def get_patch(self
, name
):
341 """Return a Patch object for the given name
343 return Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
345 def get_current_patch(self
):
346 """Return a Patch object representing the topmost patch, or
347 None if there is no such patch."""
348 crt
= self
.get_current()
351 return Patch(crt
, self
.__patch
_dir
, self
.__refs
_dir
)
353 def get_current(self
):
354 """Return the name of the topmost patch, or None if there is
356 name
= self
._get
_field
('current')
362 def get_applied(self
):
363 if not os
.path
.isfile(self
.__applied
_file
):
364 raise StackException
, 'Branch "%s" not initialised' % self
.__name
365 f
= file(self
.__applied
_file
)
366 names
= [line
.strip() for line
in f
.readlines()]
370 def get_unapplied(self
):
371 if not os
.path
.isfile(self
.__unapplied
_file
):
372 raise StackException
, 'Branch "%s" not initialised' % self
.__name
373 f
= file(self
.__unapplied
_file
)
374 names
= [line
.strip() for line
in f
.readlines()]
378 def get_hidden(self
):
379 if not os
.path
.isfile(self
.__hidden
_file
):
381 f
= file(self
.__hidden
_file
)
382 names
= [line
.strip() for line
in f
.readlines()]
386 def get_base_file(self
):
387 self
.__begin
_stack
_check
()
388 return self
.__base
_file
390 def get_protected(self
):
391 return os
.path
.isfile(os
.path
.join(self
._dir
(), 'protected'))
394 protect_file
= os
.path
.join(self
._dir
(), 'protected')
395 if not os
.path
.isfile(protect_file
):
396 create_empty_file(protect_file
)
399 protect_file
= os
.path
.join(self
._dir
(), 'protected')
400 if os
.path
.isfile(protect_file
):
401 os
.remove(protect_file
)
403 def get_description(self
):
404 return self
._get
_field
('description') or ''
406 def set_description(self
, line
):
407 self
._set
_field
('description', line
)
409 def __patch_is_current(self
, patch
):
410 return patch
.get_name() == self
.get_current()
412 def patch_applied(self
, name
):
413 """Return true if the patch exists in the applied list
415 return name
in self
.get_applied()
417 def patch_unapplied(self
, name
):
418 """Return true if the patch exists in the unapplied list
420 return name
in self
.get_unapplied()
422 def patch_hidden(self
, name
):
423 """Return true if the patch is hidden.
425 return name
in self
.get_hidden()
427 def patch_exists(self
, name
):
428 """Return true if there is a patch with the given name, false
430 return self
.patch_applied(name
) or self
.patch_unapplied(name
)
432 def __begin_stack_check(self
):
433 """Save the current HEAD into .git/refs/heads/base if the stack
436 if len(self
.get_applied()) == 0:
437 head
= git
.get_head()
438 write_string(self
.__base
_file
, head
)
440 def __end_stack_check(self
):
441 """Remove .git/refs/heads/base if the stack is empty.
442 This warning should never happen
444 if len(self
.get_applied()) == 0 \
445 and read_string(self
.__base
_file
) != git
.get_head():
446 print 'Warning: stack empty but the HEAD and base are different'
448 def head_top_equal(self
):
449 """Return true if the head and the top are the same
451 crt
= self
.get_current_patch()
453 # we don't care, no patches applied
455 return git
.get_head() == crt
.get_top()
457 def is_initialised(self
):
458 """Checks if series is already initialised
460 return os
.path
.isdir(self
.__patch
_dir
)
462 def init(self
, create_at
=False):
463 """Initialises the stgit series
465 bases_dir
= os
.path
.join(self
.__base
_dir
, 'refs', 'bases')
467 if os
.path
.exists(self
.__patch
_dir
):
468 raise StackException
, self
.__patch
_dir
+ ' already exists'
469 if os
.path
.exists(self
.__refs
_dir
):
470 raise StackException
, self
.__refs
_dir
+ ' already exists'
471 if os
.path
.exists(self
.__base
_file
):
472 raise StackException
, self
.__base
_file
+ ' already exists'
474 if (create_at
!=False):
475 git
.create_branch(self
.__name
, create_at
)
477 os
.makedirs(self
.__patch
_dir
)
479 create_dirs(bases_dir
)
481 self
.create_empty_field('applied')
482 self
.create_empty_field('unapplied')
483 self
.create_empty_field('description')
484 os
.makedirs(os
.path
.join(self
._dir
(), 'patches'))
485 os
.makedirs(self
.__refs
_dir
)
486 self
.__begin
_stack
_check
()
489 """Either convert to use a separate patch directory, or
490 unconvert to place the patches in the same directory with
493 if self
.__patch
_dir
== self
._dir
():
494 print 'Converting old-style to new-style...',
497 self
.__patch
_dir
= os
.path
.join(self
._dir
(), 'patches')
498 os
.makedirs(self
.__patch
_dir
)
500 for p
in self
.get_applied() + self
.get_unapplied():
501 src
= os
.path
.join(self
._dir
(), p
)
502 dest
= os
.path
.join(self
.__patch
_dir
, p
)
508 print 'Converting new-style to old-style...',
511 for p
in self
.get_applied() + self
.get_unapplied():
512 src
= os
.path
.join(self
.__patch
_dir
, p
)
513 dest
= os
.path
.join(self
._dir
(), p
)
516 if not os
.listdir(self
.__patch
_dir
):
517 os
.rmdir(self
.__patch
_dir
)
520 print 'Patch directory %s is not empty.' % self
.__name
522 self
.__patch
_dir
= self
._dir
()
524 def rename(self
, to_name
):
527 to_stack
= Series(to_name
)
529 if to_stack
.is_initialised():
530 raise StackException
, '"%s" already exists' % to_stack
.get_branch()
531 if os
.path
.exists(to_stack
.__base
_file
):
532 os
.remove(to_stack
.__base
_file
)
534 git
.rename_branch(self
.__name
, to_name
)
536 if os
.path
.isdir(self
._dir
()):
537 rename(os
.path
.join(self
.__base
_dir
, 'patches'),
538 self
.__name
, to_stack
.__name
)
539 if os
.path
.exists(self
.__base
_file
):
540 rename(os
.path
.join(self
.__base
_dir
, 'refs', 'bases'),
541 self
.__name
, to_stack
.__name
)
542 if os
.path
.exists(self
.__refs
_dir
):
543 rename(os
.path
.join(self
.__base
_dir
, 'refs', 'patches'),
544 self
.__name
, to_stack
.__name
)
546 self
.__init
__(to_name
)
548 def clone(self
, target_series
):
552 # allow cloning of branches not under StGIT control
553 base
= read_string(self
.get_base_file())
555 base
= git
.get_head()
556 Series(target_series
).init(create_at
= base
)
557 new_series
= Series(target_series
)
559 # generate an artificial description file
560 new_series
.set_description('clone of "%s"' % self
.__name
)
562 # clone self's entire series as unapplied patches
564 # allow cloning of branches not under StGIT control
565 applied
= self
.get_applied()
566 unapplied
= self
.get_unapplied()
567 patches
= applied
+ unapplied
570 patches
= applied
= unapplied
= []
572 patch
= self
.get_patch(p
)
573 new_series
.new_patch(p
, message
= patch
.get_description(),
574 can_edit
= False, unapplied
= True,
575 bottom
= patch
.get_bottom(),
576 top
= patch
.get_top(),
577 author_name
= patch
.get_authname(),
578 author_email
= patch
.get_authemail(),
579 author_date
= patch
.get_authdate())
581 # fast forward the cloned series to self's top
582 new_series
.forward_patches(applied
)
584 def delete(self
, force
= False):
585 """Deletes an stgit series
587 if self
.is_initialised():
588 patches
= self
.get_unapplied() + self
.get_applied()
589 if not force
and patches
:
590 raise StackException
, \
591 'Cannot delete: the series still contains patches'
593 Patch(p
, self
.__patch
_dir
, self
.__refs
_dir
).delete()
595 # remove the trash directory
596 for fname
in os
.listdir(self
.__trash
_dir
):
598 os
.rmdir(self
.__trash
_dir
)
600 # FIXME: find a way to get rid of those manual removals
601 # (move functionnality to StgitObject ?)
602 if os
.path
.exists(self
.__applied
_file
):
603 os
.remove(self
.__applied
_file
)
604 if os
.path
.exists(self
.__unapplied
_file
):
605 os
.remove(self
.__unapplied
_file
)
606 if os
.path
.exists(self
.__hidden
_file
):
607 os
.remove(self
.__hidden
_file
)
608 if os
.path
.exists(self
.__current
_file
):
609 os
.remove(self
.__current
_file
)
610 if os
.path
.exists(self
.__descr
_file
):
611 os
.remove(self
.__descr
_file
)
612 if not os
.listdir(self
.__patch
_dir
):
613 os
.rmdir(self
.__patch
_dir
)
615 print 'Patch directory %s is not empty.' % self
.__name
616 if not os
.listdir(self
._dir
()):
617 remove_dirs(os
.path
.join(self
.__base
_dir
, 'patches'),
620 print 'Series directory %s is not empty.' % self
.__name
621 if not os
.listdir(self
.__refs
_dir
):
622 remove_dirs(os
.path
.join(self
.__base
_dir
, 'refs', 'patches'),
625 print 'Refs directory %s is not empty.' % self
.__refs
_dir
627 if os
.path
.exists(self
.__base
_file
):
628 remove_file_and_dirs(
629 os
.path
.join(self
.__base
_dir
, 'refs', 'bases'), self
.__name
)
631 def refresh_patch(self
, files
= None, message
= None, edit
= False,
634 author_name
= None, author_email
= None,
636 committer_name
= None, committer_email
= None,
637 backup
= False, sign_str
= None, log
= 'refresh'):
638 """Generates a new commit for the given patch
640 name
= self
.get_current()
642 raise StackException
, 'No patches applied'
644 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
646 descr
= patch
.get_description()
647 if not (message
or descr
):
653 if not message
and edit
:
654 descr
= edit_file(self
, descr
.rstrip(), \
655 'Please edit the description for patch "%s" ' \
656 'above.' % name
, show_patch
)
659 author_name
= patch
.get_authname()
661 author_email
= patch
.get_authemail()
663 author_date
= patch
.get_authdate()
664 if not committer_name
:
665 committer_name
= patch
.get_commname()
666 if not committer_email
:
667 committer_email
= patch
.get_commemail()
670 descr
= '%s\n%s: %s <%s>\n' % (descr
.rstrip(), sign_str
,
671 committer_name
, committer_email
)
673 bottom
= patch
.get_bottom()
675 commit_id
= git
.commit(files
= files
,
676 message
= descr
, parents
= [bottom
],
677 cache_update
= cache_update
,
679 author_name
= author_name
,
680 author_email
= author_email
,
681 author_date
= author_date
,
682 committer_name
= committer_name
,
683 committer_email
= committer_email
)
685 patch
.set_bottom(bottom
, backup
= backup
)
686 patch
.set_top(commit_id
, backup
= backup
)
687 patch
.set_description(descr
)
688 patch
.set_authname(author_name
)
689 patch
.set_authemail(author_email
)
690 patch
.set_authdate(author_date
)
691 patch
.set_commname(committer_name
)
692 patch
.set_commemail(committer_email
)
695 self
.log_patch(patch
, log
)
699 def undo_refresh(self
):
700 """Undo the patch boundaries changes caused by 'refresh'
702 name
= self
.get_current()
705 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
706 old_bottom
= patch
.get_old_bottom()
707 old_top
= patch
.get_old_top()
709 # the bottom of the patch is not changed by refresh. If the
710 # old_bottom is different, there wasn't any previous 'refresh'
711 # command (probably only a 'push')
712 if old_bottom
!= patch
.get_bottom() or old_top
== patch
.get_top():
713 raise StackException
, 'No undo information available'
715 git
.reset(tree_id
= old_top
, check_out
= False)
716 if patch
.restore_old_boundaries():
717 self
.log_patch(patch
, 'undo')
719 def new_patch(self
, name
, message
= None, can_edit
= True,
720 unapplied
= False, show_patch
= False,
721 top
= None, bottom
= None,
722 author_name
= None, author_email
= None, author_date
= None,
723 committer_name
= None, committer_email
= None,
724 before_existing
= False, refresh
= True):
725 """Creates a new patch
727 if self
.patch_applied(name
) or self
.patch_unapplied(name
):
728 raise StackException
, 'Patch "%s" already exists' % name
730 if not message
and can_edit
:
731 descr
= edit_file(self
, None, \
732 'Please enter the description for patch "%s" ' \
733 'above.' % name
, show_patch
)
737 head
= git
.get_head()
739 self
.__begin
_stack
_check
()
741 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
745 patch
.set_bottom(bottom
)
747 patch
.set_bottom(head
)
753 patch
.set_description(descr
)
754 patch
.set_authname(author_name
)
755 patch
.set_authemail(author_email
)
756 patch
.set_authdate(author_date
)
757 patch
.set_commname(committer_name
)
758 patch
.set_commemail(committer_email
)
761 self
.log_patch(patch
, 'new')
763 patches
= [patch
.get_name()] + self
.get_unapplied()
765 f
= file(self
.__unapplied
_file
, 'w+')
766 f
.writelines([line
+ '\n' for line
in patches
])
768 elif before_existing
:
769 self
.log_patch(patch
, 'new')
771 insert_string(self
.__applied
_file
, patch
.get_name())
772 if not self
.get_current():
773 self
.__set
_current
(name
)
775 append_string(self
.__applied
_file
, patch
.get_name())
776 self
.__set
_current
(name
)
778 self
.refresh_patch(cache_update
= False, log
= 'new')
780 def delete_patch(self
, name
):
783 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
785 if self
.__patch
_is
_current
(patch
):
787 elif self
.patch_applied(name
):
788 raise StackException
, 'Cannot remove an applied patch, "%s", ' \
789 'which is not current' % name
790 elif not name
in self
.get_unapplied():
791 raise StackException
, 'Unknown patch "%s"' % name
793 # save the commit id to a trash file
794 write_string(os
.path
.join(self
.__trash
_dir
, name
), patch
.get_top())
798 unapplied
= self
.get_unapplied()
799 unapplied
.remove(name
)
800 f
= file(self
.__unapplied
_file
, 'w+')
801 f
.writelines([line
+ '\n' for line
in unapplied
])
804 if self
.patch_hidden(name
):
805 self
.unhide_patch(name
)
807 self
.__begin
_stack
_check
()
809 def forward_patches(self
, names
):
810 """Try to fast-forward an array of patches.
812 On return, patches in names[0:returned_value] have been pushed on the
813 stack. Apply the rest with push_patch
815 unapplied
= self
.get_unapplied()
816 self
.__begin
_stack
_check
()
822 assert(name
in unapplied
)
824 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
827 bottom
= patch
.get_bottom()
828 top
= patch
.get_top()
830 # top != bottom always since we have a commit for each patch
832 # reset the backup information. No logging since the
833 # patch hasn't changed
834 patch
.set_bottom(head
, backup
= True)
835 patch
.set_top(top
, backup
= True)
838 head_tree
= git
.get_commit(head
).get_tree()
839 bottom_tree
= git
.get_commit(bottom
).get_tree()
840 if head_tree
== bottom_tree
:
841 # We must just reparent this patch and create a new commit
843 descr
= patch
.get_description()
844 author_name
= patch
.get_authname()
845 author_email
= patch
.get_authemail()
846 author_date
= patch
.get_authdate()
847 committer_name
= patch
.get_commname()
848 committer_email
= patch
.get_commemail()
850 top_tree
= git
.get_commit(top
).get_tree()
852 top
= git
.commit(message
= descr
, parents
= [head
],
853 cache_update
= False,
856 author_name
= author_name
,
857 author_email
= author_email
,
858 author_date
= author_date
,
859 committer_name
= committer_name
,
860 committer_email
= committer_email
)
862 patch
.set_bottom(head
, backup
= True)
863 patch
.set_top(top
, backup
= True)
865 self
.log_patch(patch
, 'push(f)')
868 # stop the fast-forwarding, must do a real merge
872 unapplied
.remove(name
)
879 append_strings(self
.__applied
_file
, names
[0:forwarded
])
881 f
= file(self
.__unapplied
_file
, 'w+')
882 f
.writelines([line
+ '\n' for line
in unapplied
])
885 self
.__set
_current
(name
)
889 def merged_patches(self
, names
):
890 """Test which patches were merged upstream by reverse-applying
891 them in reverse order. The function returns the list of
892 patches detected to have been applied. The state of the tree
893 is restored to the original one
895 patches
= [Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
901 if git
.apply_diff(p
.get_top(), p
.get_bottom()):
902 merged
.append(p
.get_name())
909 def push_patch(self
, name
, empty
= False):
910 """Pushes a patch on the stack
912 unapplied
= self
.get_unapplied()
913 assert(name
in unapplied
)
915 self
.__begin
_stack
_check
()
917 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
919 head
= git
.get_head()
920 bottom
= patch
.get_bottom()
921 top
= patch
.get_top()
926 # top != bottom always since we have a commit for each patch
928 # just make an empty patch (top = bottom = HEAD). This
929 # option is useful to allow undoing already merged
930 # patches. The top is updated by refresh_patch since we
931 # need an empty commit
932 patch
.set_bottom(head
, backup
= True)
933 patch
.set_top(head
, backup
= True)
936 # reset the backup information. No need for logging
937 patch
.set_bottom(bottom
, backup
= True)
938 patch
.set_top(top
, backup
= True)
942 # new patch needs to be refreshed.
943 # The current patch is empty after merge.
944 patch
.set_bottom(head
, backup
= True)
945 patch
.set_top(head
, backup
= True)
947 # Try the fast applying first. If this fails, fall back to the
949 if not git
.apply_diff(bottom
, top
):
950 # if git.apply_diff() fails, the patch requires a diff3
951 # merge and can be reported as modified
954 # merge can fail but the patch needs to be pushed
956 git
.merge(bottom
, head
, top
, recursive
= True)
957 except git
.GitException
, ex
:
958 print >> sys
.stderr
, \
959 'The merge failed during "push". ' \
960 'Use "refresh" after fixing the conflicts'
962 append_string(self
.__applied
_file
, name
)
964 unapplied
.remove(name
)
965 f
= file(self
.__unapplied
_file
, 'w+')
966 f
.writelines([line
+ '\n' for line
in unapplied
])
969 self
.__set
_current
(name
)
971 # head == bottom case doesn't need to refresh the patch
972 if empty
or head
!= bottom
:
974 # if the merge was OK and no conflicts, just refresh the patch
975 # The GIT cache was already updated by the merge operation
980 self
.refresh_patch(cache_update
= False, log
= log
)
982 # we store the correctly merged files only for
983 # tracking the conflict history. Note that the
984 # git.merge() operations shouls always leave the index
985 # in a valid state (i.e. only stage 0 files)
986 self
.refresh_patch(cache_update
= False, log
= 'push(c)')
987 raise StackException
, str(ex
)
992 name
= self
.get_current()
995 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
996 old_bottom
= patch
.get_old_bottom()
997 old_top
= patch
.get_old_top()
999 # the top of the patch is changed by a push operation only
1000 # together with the bottom (otherwise the top was probably
1001 # modified by 'refresh'). If they are both unchanged, there
1002 # was a fast forward
1003 if old_bottom
== patch
.get_bottom() and old_top
!= patch
.get_top():
1004 raise StackException
, 'No undo information available'
1007 self
.pop_patch(name
)
1008 ret
= patch
.restore_old_boundaries()
1010 self
.log_patch(patch
, 'undo')
1014 def pop_patch(self
, name
, keep
= False):
1015 """Pops the top patch from the stack
1017 applied
= self
.get_applied()
1019 assert(name
in applied
)
1021 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
1023 # only keep the local changes
1024 if keep
and not git
.apply_diff(git
.get_head(), patch
.get_bottom()):
1025 raise StackException
, \
1026 'Failed to pop patches while preserving the local changes'
1028 git
.switch(patch
.get_bottom(), keep
)
1030 # save the new applied list
1031 idx
= applied
.index(name
) + 1
1033 popped
= applied
[:idx
]
1035 unapplied
= popped
+ self
.get_unapplied()
1037 f
= file(self
.__unapplied
_file
, 'w+')
1038 f
.writelines([line
+ '\n' for line
in unapplied
])
1044 f
= file(self
.__applied
_file
, 'w+')
1045 f
.writelines([line
+ '\n' for line
in applied
])
1049 self
.__set
_current
(None)
1051 self
.__set
_current
(applied
[-1])
1053 self
.__end
_stack
_check
()
1055 def empty_patch(self
, name
):
1056 """Returns True if the patch is empty
1058 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
1059 bottom
= patch
.get_bottom()
1060 top
= patch
.get_top()
1064 elif git
.get_commit(top
).get_tree() \
1065 == git
.get_commit(bottom
).get_tree():
1070 def rename_patch(self
, oldname
, newname
):
1071 applied
= self
.get_applied()
1072 unapplied
= self
.get_unapplied()
1074 if oldname
== newname
:
1075 raise StackException
, '"To" name and "from" name are the same'
1077 if newname
in applied
or newname
in unapplied
:
1078 raise StackException
, 'Patch "%s" already exists' % newname
1080 if self
.patch_hidden(oldname
):
1081 self
.unhide_patch(oldname
)
1082 self
.hide_patch(newname
)
1084 if oldname
in unapplied
:
1085 Patch(oldname
, self
.__patch
_dir
, self
.__refs
_dir
).rename(newname
)
1086 unapplied
[unapplied
.index(oldname
)] = newname
1088 f
= file(self
.__unapplied
_file
, 'w+')
1089 f
.writelines([line
+ '\n' for line
in unapplied
])
1091 elif oldname
in applied
:
1092 Patch(oldname
, self
.__patch
_dir
, self
.__refs
_dir
).rename(newname
)
1093 if oldname
== self
.get_current():
1094 self
.__set
_current
(newname
)
1096 applied
[applied
.index(oldname
)] = newname
1098 f
= file(self
.__applied
_file
, 'w+')
1099 f
.writelines([line
+ '\n' for line
in applied
])
1102 raise StackException
, 'Unknown patch "%s"' % oldname
1104 def log_patch(self
, patch
, message
):
1105 """Generate a log commit for a patch
1107 top
= git
.get_commit(patch
.get_top())
1108 msg
= '%s\t%s' % (message
, top
.get_id_hash())
1110 old_log
= patch
.get_log()
1116 log
= git
.commit(message
= msg
, parents
= parents
,
1117 cache_update
= False, tree_id
= top
.get_tree(),
1121 def hide_patch(self
, name
):
1122 """Add the patch to the hidden list.
1124 if not self
.patch_exists(name
):
1125 raise StackException
, 'Unknown patch "%s"' % name
1126 elif self
.patch_hidden(name
):
1127 raise StackException
, 'Patch "%s" already hidden' % name
1129 append_string(self
.__hidden
_file
, name
)
1131 def unhide_patch(self
, name
):
1132 """Add the patch to the hidden list.
1134 if not self
.patch_exists(name
):
1135 raise StackException
, 'Unknown patch "%s"' % name
1136 hidden
= self
.get_hidden()
1137 if not name
in hidden
:
1138 raise StackException
, 'Patch "%s" not hidden' % name
1142 f
= file(self
.__hidden
_file
, 'w+')
1143 f
.writelines([line
+ '\n' for line
in hidden
])