1 """Basic quilt-like functionality
5 Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License version 2 as
9 published by the Free Software Foundation.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 from stgit
.utils
import *
24 from stgit
import git
, basedir
, templates
25 from stgit
.config
import config
28 # stack exception class
29 class StackException(Exception):
34 self
.should_print
= True
35 def __call__(self
, x
, until_test
, prefix
):
37 self
.should_print
= False
39 return x
[0:len(prefix
)] != prefix
45 __comment_prefix
= 'STG:'
46 __patch_prefix
= 'STG_PATCH:'
48 def __clean_comments(f
):
49 """Removes lines marked for status in a commit file
53 # remove status-prefixed lines
56 patch_filter
= FilterUntil()
57 until_test
= lambda t
: t
== (__patch_prefix
+ '\n')
58 lines
= [l
for l
in lines
if patch_filter(l
, until_test
, __comment_prefix
)]
60 # remove empty lines at the end
61 while len(lines
) != 0 and lines
[-1] == '\n':
64 f
.seek(0); f
.truncate()
67 def edit_file(series
, line
, comment
, show_patch
= True):
68 fname
= '.stgitmsg.txt'
69 tmpl
= templates
.get_template('patchdescr.tmpl')
78 print >> f
, __comment_prefix
, comment
79 print >> f
, __comment_prefix
, \
80 'Lines prefixed with "%s" will be automatically removed.' \
82 print >> f
, __comment_prefix
, \
83 'Trailing empty lines will be automatically removed.'
86 print >> f
, __patch_prefix
87 # series.get_patch(series.get_current()).get_top()
88 git
.diff([], series
.get_patch(series
.get_current()).get_bottom(), None, f
)
90 #Vim modeline must be near the end.
91 print >> f
, __comment_prefix
, 'vi: set textwidth=75 filetype=diff nobackup:'
95 if config
.has_option('stgit', 'editor'):
96 editor
= config
.get('stgit', 'editor')
97 elif 'EDITOR' in os
.environ
:
98 editor
= os
.environ
['EDITOR']
101 editor
+= ' %s' % fname
103 print 'Invoking the editor: "%s"...' % editor
,
105 print 'done (exit code: %d)' % os
.system(editor
)
107 f
= file(fname
, 'r+')
123 """Basic patch implementation
125 def __init__(self
, name
, series_dir
, refs_dir
):
126 self
.__series
_dir
= series_dir
128 self
.__dir
= os
.path
.join(self
.__series
_dir
, self
.__name
)
129 self
.__refs
_dir
= refs_dir
130 self
.__top
_ref
_file
= os
.path
.join(self
.__refs
_dir
, self
.__name
)
131 self
.__log
_ref
_file
= os
.path
.join(self
.__refs
_dir
,
132 self
.__name
+ '.log')
136 create_empty_file(os
.path
.join(self
.__dir
, 'bottom'))
137 create_empty_file(os
.path
.join(self
.__dir
, 'top'))
140 for f
in os
.listdir(self
.__dir
):
141 os
.remove(os
.path
.join(self
.__dir
, f
))
143 os
.remove(self
.__top
_ref
_file
)
144 if os
.path
.exists(self
.__log
_ref
_file
):
145 os
.remove(self
.__log
_ref
_file
)
150 def rename(self
, newname
):
152 old_top_ref_file
= self
.__top
_ref
_file
153 old_log_ref_file
= self
.__log
_ref
_file
154 self
.__name
= newname
155 self
.__dir
= os
.path
.join(self
.__series
_dir
, self
.__name
)
156 self
.__top
_ref
_file
= os
.path
.join(self
.__refs
_dir
, self
.__name
)
157 self
.__log
_ref
_file
= os
.path
.join(self
.__refs
_dir
,
158 self
.__name
+ '.log')
160 os
.rename(olddir
, self
.__dir
)
161 os
.rename(old_top_ref_file
, self
.__top
_ref
_file
)
162 if os
.path
.exists(old_log_ref_file
):
163 os
.rename(old_log_ref_file
, self
.__log
_ref
_file
)
165 def __update_top_ref(self
, ref
):
166 write_string(self
.__top
_ref
_file
, ref
)
168 def __update_log_ref(self
, ref
):
169 write_string(self
.__log
_ref
_file
, ref
)
171 def update_top_ref(self
):
174 self
.__update
_top
_ref
(top
)
176 def __get_field(self
, name
, multiline
= False):
177 id_file
= os
.path
.join(self
.__dir
, name
)
178 if os
.path
.isfile(id_file
):
179 line
= read_string(id_file
, multiline
)
187 def __set_field(self
, name
, value
, multiline
= False):
188 fname
= os
.path
.join(self
.__dir
, name
)
189 if value
and value
!= '':
190 write_string(fname
, value
, multiline
)
191 elif os
.path
.isfile(fname
):
194 def get_old_bottom(self
):
195 return self
.__get
_field
('bottom.old')
197 def get_bottom(self
):
198 return self
.__get
_field
('bottom')
200 def set_bottom(self
, value
, backup
= False):
202 curr
= self
.__get
_field
('bottom')
203 self
.__set
_field
('bottom.old', curr
)
204 self
.__set
_field
('bottom', value
)
206 def get_old_top(self
):
207 return self
.__get
_field
('top.old')
210 return self
.__get
_field
('top')
212 def set_top(self
, value
, backup
= False):
214 curr
= self
.__get
_field
('top')
215 self
.__set
_field
('top.old', curr
)
216 self
.__set
_field
('top', value
)
217 self
.__update
_top
_ref
(value
)
219 def restore_old_boundaries(self
):
220 bottom
= self
.__get
_field
('bottom.old')
221 top
= self
.__get
_field
('top.old')
224 self
.__set
_field
('bottom', bottom
)
225 self
.__set
_field
('top', top
)
226 self
.__update
_top
_ref
(top
)
231 def get_description(self
):
232 return self
.__get
_field
('description', True)
234 def set_description(self
, line
):
235 self
.__set
_field
('description', line
, True)
237 def get_authname(self
):
238 return self
.__get
_field
('authname')
240 def set_authname(self
, name
):
242 if config
.has_option('stgit', 'authname'):
243 name
= config
.get('stgit', 'authname')
244 elif 'GIT_AUTHOR_NAME' in os
.environ
:
245 name
= os
.environ
['GIT_AUTHOR_NAME']
246 self
.__set
_field
('authname', name
)
248 def get_authemail(self
):
249 return self
.__get
_field
('authemail')
251 def set_authemail(self
, address
):
253 if config
.has_option('stgit', 'authemail'):
254 address
= config
.get('stgit', 'authemail')
255 elif 'GIT_AUTHOR_EMAIL' in os
.environ
:
256 address
= os
.environ
['GIT_AUTHOR_EMAIL']
257 self
.__set
_field
('authemail', address
)
259 def get_authdate(self
):
260 return self
.__get
_field
('authdate')
262 def set_authdate(self
, date
):
263 if not date
and 'GIT_AUTHOR_DATE' in os
.environ
:
264 date
= os
.environ
['GIT_AUTHOR_DATE']
265 self
.__set
_field
('authdate', date
)
267 def get_commname(self
):
268 return self
.__get
_field
('commname')
270 def set_commname(self
, name
):
272 if config
.has_option('stgit', 'commname'):
273 name
= config
.get('stgit', 'commname')
274 elif 'GIT_COMMITTER_NAME' in os
.environ
:
275 name
= os
.environ
['GIT_COMMITTER_NAME']
276 self
.__set
_field
('commname', name
)
278 def get_commemail(self
):
279 return self
.__get
_field
('commemail')
281 def set_commemail(self
, address
):
283 if config
.has_option('stgit', 'commemail'):
284 address
= config
.get('stgit', 'commemail')
285 elif 'GIT_COMMITTER_EMAIL' in os
.environ
:
286 address
= os
.environ
['GIT_COMMITTER_EMAIL']
287 self
.__set
_field
('commemail', address
)
290 return self
.__get
_field
('log')
292 def set_log(self
, value
, backup
= False):
293 self
.__set
_field
('log', value
)
294 self
.__update
_log
_ref
(value
)
298 """Class including the operations on series
300 def __init__(self
, name
= None):
301 """Takes a series name as the parameter.
307 self
.__name
= git
.get_head_file()
308 self
.__base
_dir
= basedir
.get()
309 except git
.GitException
, ex
:
310 raise StackException
, 'GIT tree not initialised: %s' % ex
312 self
.__series
_dir
= os
.path
.join(self
.__base
_dir
, 'patches',
314 self
.__refs
_dir
= os
.path
.join(self
.__base
_dir
, 'refs', 'patches',
316 self
.__base
_file
= os
.path
.join(self
.__base
_dir
, 'refs', 'bases',
319 self
.__applied
_file
= os
.path
.join(self
.__series
_dir
, 'applied')
320 self
.__unapplied
_file
= os
.path
.join(self
.__series
_dir
, 'unapplied')
321 self
.__current
_file
= os
.path
.join(self
.__series
_dir
, 'current')
322 self
.__descr
_file
= os
.path
.join(self
.__series
_dir
, 'description')
324 # where this series keeps its patches
325 self
.__patch
_dir
= os
.path
.join(self
.__series
_dir
, 'patches')
326 if not os
.path
.isdir(self
.__patch
_dir
):
327 self
.__patch
_dir
= self
.__series
_dir
329 # if no __refs_dir, create and populate it (upgrade old repositories)
330 if self
.is_initialised() and not os
.path
.isdir(self
.__refs
_dir
):
331 os
.makedirs(self
.__refs
_dir
)
332 for patch
in self
.get_applied() + self
.get_unapplied():
333 self
.get_patch(patch
).update_top_ref()
335 def get_branch(self
):
336 """Return the branch name for the Series object
340 def __set_current(self
, name
):
341 """Sets the topmost patch
344 write_string(self
.__current
_file
, name
)
346 create_empty_file(self
.__current
_file
)
348 def get_patch(self
, name
):
349 """Return a Patch object for the given name
351 return Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
353 def get_current(self
):
354 """Return a Patch object representing the topmost patch
356 if os
.path
.isfile(self
.__current
_file
):
357 name
= read_string(self
.__current
_file
)
365 def get_applied(self
):
366 if not os
.path
.isfile(self
.__applied
_file
):
367 raise StackException
, 'Branch "%s" not initialised' % self
.__name
368 f
= file(self
.__applied
_file
)
369 names
= [line
.strip() for line
in f
.readlines()]
373 def get_unapplied(self
):
374 if not os
.path
.isfile(self
.__unapplied
_file
):
375 raise StackException
, 'Branch "%s" not initialised' % self
.__name
376 f
= file(self
.__unapplied
_file
)
377 names
= [line
.strip() for line
in f
.readlines()]
381 def get_base_file(self
):
382 self
.__begin
_stack
_check
()
383 return self
.__base
_file
385 def get_protected(self
):
386 return os
.path
.isfile(os
.path
.join(self
.__series
_dir
, 'protected'))
389 protect_file
= os
.path
.join(self
.__series
_dir
, 'protected')
390 if not os
.path
.isfile(protect_file
):
391 create_empty_file(protect_file
)
394 protect_file
= os
.path
.join(self
.__series
_dir
, 'protected')
395 if os
.path
.isfile(protect_file
):
396 os
.remove(protect_file
)
398 def get_description(self
):
399 if os
.path
.isfile(self
.__descr
_file
):
400 return read_string(self
.__descr
_file
)
404 def __patch_is_current(self
, patch
):
405 return patch
.get_name() == read_string(self
.__current
_file
)
407 def __patch_applied(self
, name
):
408 """Return true if the patch exists in the applied list
410 return name
in self
.get_applied()
412 def __patch_unapplied(self
, name
):
413 """Return true if the patch exists in the unapplied list
415 return name
in self
.get_unapplied()
417 def __begin_stack_check(self
):
418 """Save the current HEAD into .git/refs/heads/base if the stack
421 if len(self
.get_applied()) == 0:
422 head
= git
.get_head()
423 write_string(self
.__base
_file
, head
)
425 def __end_stack_check(self
):
426 """Remove .git/refs/heads/base if the stack is empty.
427 This warning should never happen
429 if len(self
.get_applied()) == 0 \
430 and read_string(self
.__base
_file
) != git
.get_head():
431 print 'Warning: stack empty but the HEAD and base are different'
433 def head_top_equal(self
):
434 """Return true if the head and the top are the same
436 crt
= self
.get_current()
438 # we don't care, no patches applied
440 return git
.get_head() == Patch(crt
, self
.__patch
_dir
,
441 self
.__refs
_dir
).get_top()
443 def is_initialised(self
):
444 """Checks if series is already initialised
446 return os
.path
.isdir(self
.__patch
_dir
)
448 def init(self
, create_at
=False):
449 """Initialises the stgit series
451 bases_dir
= os
.path
.join(self
.__base
_dir
, 'refs', 'bases')
453 if os
.path
.exists(self
.__patch
_dir
):
454 raise StackException
, self
.__patch
_dir
+ ' already exists'
455 if os
.path
.exists(self
.__refs
_dir
):
456 raise StackException
, self
.__refs
_dir
+ ' already exists'
457 if os
.path
.exists(self
.__base
_file
):
458 raise StackException
, self
.__base
_file
+ ' already exists'
460 if (create_at
!=False):
461 git
.create_branch(self
.__name
, create_at
)
463 os
.makedirs(self
.__patch
_dir
)
465 create_dirs(bases_dir
)
467 create_empty_file(self
.__applied
_file
)
468 create_empty_file(self
.__unapplied
_file
)
469 create_empty_file(self
.__descr
_file
)
470 os
.makedirs(os
.path
.join(self
.__series
_dir
, 'patches'))
471 os
.makedirs(self
.__refs
_dir
)
472 self
.__begin
_stack
_check
()
475 """Either convert to use a separate patch directory, or
476 unconvert to place the patches in the same directory with
479 if self
.__patch
_dir
== self
.__series
_dir
:
480 print 'Converting old-style to new-style...',
483 self
.__patch
_dir
= os
.path
.join(self
.__series
_dir
, 'patches')
484 os
.makedirs(self
.__patch
_dir
)
486 for p
in self
.get_applied() + self
.get_unapplied():
487 src
= os
.path
.join(self
.__series
_dir
, p
)
488 dest
= os
.path
.join(self
.__patch
_dir
, p
)
494 print 'Converting new-style to old-style...',
497 for p
in self
.get_applied() + self
.get_unapplied():
498 src
= os
.path
.join(self
.__patch
_dir
, p
)
499 dest
= os
.path
.join(self
.__series
_dir
, p
)
502 if not os
.listdir(self
.__patch
_dir
):
503 os
.rmdir(self
.__patch
_dir
)
506 print 'Patch directory %s is not empty.' % self
.__name
508 self
.__patch
_dir
= self
.__series
_dir
510 def rename(self
, to_name
):
513 to_stack
= Series(to_name
)
515 if to_stack
.is_initialised():
516 raise StackException
, '"%s" already exists' % to_stack
.get_branch()
517 if os
.path
.exists(to_stack
.__base
_file
):
518 os
.remove(to_stack
.__base
_file
)
520 git
.rename_branch(self
.__name
, to_name
)
522 if os
.path
.isdir(self
.__series
_dir
):
523 rename(os
.path
.join(self
.__base
_dir
, 'patches'),
524 self
.__name
, to_stack
.__name
)
525 if os
.path
.exists(self
.__base
_file
):
526 rename(os
.path
.join(self
.__base
_dir
, 'refs', 'bases'),
527 self
.__name
, to_stack
.__name
)
528 if os
.path
.exists(self
.__refs
_dir
):
529 rename(os
.path
.join(self
.__base
_dir
, 'refs', 'patches'),
530 self
.__name
, to_stack
.__name
)
532 self
.__init
__(to_name
)
534 def clone(self
, target_series
):
537 base
= read_string(self
.get_base_file())
538 Series(target_series
).init(create_at
= base
)
539 new_series
= Series(target_series
)
541 # generate an artificial description file
542 write_string(new_series
.__descr
_file
, 'clone of "%s"' % self
.__name
)
544 # clone self's entire series as unapplied patches
545 patches
= self
.get_applied() + self
.get_unapplied()
548 patch
= self
.get_patch(p
)
549 new_series
.new_patch(p
, message
= patch
.get_description(),
550 can_edit
= False, unapplied
= True,
551 bottom
= patch
.get_bottom(),
552 top
= patch
.get_top(),
553 author_name
= patch
.get_authname(),
554 author_email
= patch
.get_authemail(),
555 author_date
= patch
.get_authdate())
557 # fast forward the cloned series to self's top
558 new_series
.forward_patches(self
.get_applied())
560 def delete(self
, force
= False):
561 """Deletes an stgit series
563 if self
.is_initialised():
564 patches
= self
.get_unapplied() + self
.get_applied()
565 if not force
and patches
:
566 raise StackException
, \
567 'Cannot delete: the series still contains patches'
569 Patch(p
, self
.__patch
_dir
, self
.__refs
_dir
).delete()
571 if os
.path
.exists(self
.__applied
_file
):
572 os
.remove(self
.__applied
_file
)
573 if os
.path
.exists(self
.__unapplied
_file
):
574 os
.remove(self
.__unapplied
_file
)
575 if os
.path
.exists(self
.__current
_file
):
576 os
.remove(self
.__current
_file
)
577 if os
.path
.exists(self
.__descr
_file
):
578 os
.remove(self
.__descr
_file
)
579 if not os
.listdir(self
.__patch
_dir
):
580 os
.rmdir(self
.__patch
_dir
)
582 print 'Patch directory %s is not empty.' % self
.__name
583 if not os
.listdir(self
.__series
_dir
):
584 remove_dirs(os
.path
.join(self
.__base
_dir
, 'patches'),
587 print 'Series directory %s is not empty.' % self
.__name
588 if not os
.listdir(self
.__refs
_dir
):
589 remove_dirs(os
.path
.join(self
.__base
_dir
, 'refs', 'patches'),
592 print 'Refs directory %s is not empty.' % self
.__refs
_dir
594 if os
.path
.exists(self
.__base
_file
):
595 remove_file_and_dirs(
596 os
.path
.join(self
.__base
_dir
, 'refs', 'bases'), self
.__name
)
598 def refresh_patch(self
, files
= None, message
= None, edit
= False,
601 author_name
= None, author_email
= None,
603 committer_name
= None, committer_email
= None,
604 backup
= False, log
= 'refresh'):
605 """Generates a new commit for the given patch
607 name
= self
.get_current()
609 raise StackException
, 'No patches applied'
611 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
613 descr
= patch
.get_description()
614 if not (message
or descr
):
620 if not message
and edit
:
621 descr
= edit_file(self
, descr
.rstrip(), \
622 'Please edit the description for patch "%s" ' \
623 'above.' % name
, show_patch
)
626 author_name
= patch
.get_authname()
628 author_email
= patch
.get_authemail()
630 author_date
= patch
.get_authdate()
631 if not committer_name
:
632 committer_name
= patch
.get_commname()
633 if not committer_email
:
634 committer_email
= patch
.get_commemail()
636 bottom
= patch
.get_bottom()
638 commit_id
= git
.commit(files
= files
,
639 message
= descr
, parents
= [bottom
],
640 cache_update
= cache_update
,
642 author_name
= author_name
,
643 author_email
= author_email
,
644 author_date
= author_date
,
645 committer_name
= committer_name
,
646 committer_email
= committer_email
)
648 patch
.set_bottom(bottom
, backup
= backup
)
649 patch
.set_top(commit_id
, backup
= backup
)
650 patch
.set_description(descr
)
651 patch
.set_authname(author_name
)
652 patch
.set_authemail(author_email
)
653 patch
.set_authdate(author_date
)
654 patch
.set_commname(committer_name
)
655 patch
.set_commemail(committer_email
)
658 self
.log_patch(patch
, log
)
662 def undo_refresh(self
):
663 """Undo the patch boundaries changes caused by 'refresh'
665 name
= self
.get_current()
668 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
669 old_bottom
= patch
.get_old_bottom()
670 old_top
= patch
.get_old_top()
672 # the bottom of the patch is not changed by refresh. If the
673 # old_bottom is different, there wasn't any previous 'refresh'
674 # command (probably only a 'push')
675 if old_bottom
!= patch
.get_bottom() or old_top
== patch
.get_top():
676 raise StackException
, 'No refresh undo information available'
678 git
.reset(tree_id
= old_top
, check_out
= False)
679 if patch
.restore_old_boundaries():
680 self
.log_patch(patch
, 'undo')
682 def new_patch(self
, name
, message
= None, can_edit
= True,
683 unapplied
= False, show_patch
= False,
684 top
= None, bottom
= None,
685 author_name
= None, author_email
= None, author_date
= None,
686 committer_name
= None, committer_email
= None,
687 before_existing
= False):
688 """Creates a new patch
690 if self
.__patch
_applied
(name
) or self
.__patch
_unapplied
(name
):
691 raise StackException
, 'Patch "%s" already exists' % name
693 if not message
and can_edit
:
694 descr
= edit_file(self
, None, \
695 'Please enter the description for patch "%s" ' \
696 'above.' % name
, show_patch
)
700 head
= git
.get_head()
702 self
.__begin
_stack
_check
()
704 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
708 patch
.set_bottom(bottom
)
710 patch
.set_bottom(head
)
716 patch
.set_description(descr
)
717 patch
.set_authname(author_name
)
718 patch
.set_authemail(author_email
)
719 patch
.set_authdate(author_date
)
720 patch
.set_commname(committer_name
)
721 patch
.set_commemail(committer_email
)
724 self
.log_patch(patch
, 'new')
726 patches
= [patch
.get_name()] + self
.get_unapplied()
728 f
= file(self
.__unapplied
_file
, 'w+')
729 f
.writelines([line
+ '\n' for line
in patches
])
731 elif before_existing
:
732 self
.log_patch(patch
, 'new')
734 insert_string(self
.__applied
_file
, patch
.get_name())
735 if not self
.get_current():
736 self
.__set
_current
(name
)
738 append_string(self
.__applied
_file
, patch
.get_name())
739 self
.__set
_current
(name
)
741 self
.refresh_patch(cache_update
= False, log
= 'new')
743 def delete_patch(self
, name
):
746 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
748 if self
.__patch
_is
_current
(patch
):
750 elif self
.__patch
_applied
(name
):
751 raise StackException
, 'Cannot remove an applied patch, "%s", ' \
752 'which is not current' % name
753 elif not name
in self
.get_unapplied():
754 raise StackException
, 'Unknown patch "%s"' % name
758 unapplied
= self
.get_unapplied()
759 unapplied
.remove(name
)
760 f
= file(self
.__unapplied
_file
, 'w+')
761 f
.writelines([line
+ '\n' for line
in unapplied
])
763 self
.__begin
_stack
_check
()
765 def forward_patches(self
, names
):
766 """Try to fast-forward an array of patches.
768 On return, patches in names[0:returned_value] have been pushed on the
769 stack. Apply the rest with push_patch
771 unapplied
= self
.get_unapplied()
772 self
.__begin
_stack
_check
()
778 assert(name
in unapplied
)
780 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
783 bottom
= patch
.get_bottom()
784 top
= patch
.get_top()
786 # top != bottom always since we have a commit for each patch
788 # reset the backup information. No logging since the
789 # patch hasn't changed
790 patch
.set_bottom(head
, backup
= True)
791 patch
.set_top(top
, backup
= True)
794 head_tree
= git
.get_commit(head
).get_tree()
795 bottom_tree
= git
.get_commit(bottom
).get_tree()
796 if head_tree
== bottom_tree
:
797 # We must just reparent this patch and create a new commit
799 descr
= patch
.get_description()
800 author_name
= patch
.get_authname()
801 author_email
= patch
.get_authemail()
802 author_date
= patch
.get_authdate()
803 committer_name
= patch
.get_commname()
804 committer_email
= patch
.get_commemail()
806 top_tree
= git
.get_commit(top
).get_tree()
808 top
= git
.commit(message
= descr
, parents
= [head
],
809 cache_update
= False,
812 author_name
= author_name
,
813 author_email
= author_email
,
814 author_date
= author_date
,
815 committer_name
= committer_name
,
816 committer_email
= committer_email
)
818 patch
.set_bottom(head
, backup
= True)
819 patch
.set_top(top
, backup
= True)
821 self
.log_patch(patch
, 'push(f)')
824 # stop the fast-forwarding, must do a real merge
828 unapplied
.remove(name
)
835 append_strings(self
.__applied
_file
, names
[0:forwarded
])
837 f
= file(self
.__unapplied
_file
, 'w+')
838 f
.writelines([line
+ '\n' for line
in unapplied
])
841 self
.__set
_current
(name
)
845 def merged_patches(self
, names
):
846 """Test which patches were merged upstream by reverse-applying
847 them in reverse order. The function returns the list of
848 patches detected to have been applied. The state of the tree
849 is restored to the original one
851 patches
= [Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
857 if git
.apply_diff(p
.get_top(), p
.get_bottom()):
858 merged
.append(p
.get_name())
865 def push_patch(self
, name
, empty
= False):
866 """Pushes a patch on the stack
868 unapplied
= self
.get_unapplied()
869 assert(name
in unapplied
)
871 self
.__begin
_stack
_check
()
873 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
875 head
= git
.get_head()
876 bottom
= patch
.get_bottom()
877 top
= patch
.get_top()
882 # top != bottom always since we have a commit for each patch
884 # just make an empty patch (top = bottom = HEAD). This
885 # option is useful to allow undoing already merged
886 # patches. The top is updated by refresh_patch since we
887 # need an empty commit
888 patch
.set_bottom(head
, backup
= True)
889 patch
.set_top(head
, backup
= True)
892 # reset the backup information. No need for logging
893 patch
.set_bottom(bottom
, backup
= True)
894 patch
.set_top(top
, backup
= True)
898 # new patch needs to be refreshed.
899 # The current patch is empty after merge.
900 patch
.set_bottom(head
, backup
= True)
901 patch
.set_top(head
, backup
= True)
903 # Try the fast applying first. If this fails, fall back to the
905 if not git
.apply_diff(bottom
, top
):
906 # if git.apply_diff() fails, the patch requires a diff3
907 # merge and can be reported as modified
910 # merge can fail but the patch needs to be pushed
912 git
.merge(bottom
, head
, top
)
913 except git
.GitException
, ex
:
914 print >> sys
.stderr
, \
915 'The merge failed during "push". ' \
916 'Use "refresh" after fixing the conflicts'
918 append_string(self
.__applied
_file
, name
)
920 unapplied
.remove(name
)
921 f
= file(self
.__unapplied
_file
, 'w+')
922 f
.writelines([line
+ '\n' for line
in unapplied
])
925 self
.__set
_current
(name
)
927 # head == bottom case doesn't need to refresh the patch
928 if empty
or head
!= bottom
:
930 # if the merge was OK and no conflicts, just refresh the patch
931 # The GIT cache was already updated by the merge operation
936 self
.refresh_patch(cache_update
= False, log
= log
)
938 raise StackException
, str(ex
)
943 name
= self
.get_current()
946 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
947 old_bottom
= patch
.get_old_bottom()
948 old_top
= patch
.get_old_top()
950 # the top of the patch is changed by a push operation only
951 # together with the bottom (otherwise the top was probably
952 # modified by 'refresh'). If they are both unchanged, there
954 if old_bottom
== patch
.get_bottom() and old_top
!= patch
.get_top():
955 raise StackException
, 'No push undo information available'
959 ret
= patch
.restore_old_boundaries()
961 self
.log_patch(patch
, 'undo')
965 def pop_patch(self
, name
, keep
= False):
966 """Pops the top patch from the stack
968 applied
= self
.get_applied()
970 assert(name
in applied
)
972 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
974 git
.switch(patch
.get_bottom(), keep
)
976 # save the new applied list
977 idx
= applied
.index(name
) + 1
979 popped
= applied
[:idx
]
981 unapplied
= popped
+ self
.get_unapplied()
983 f
= file(self
.__unapplied
_file
, 'w+')
984 f
.writelines([line
+ '\n' for line
in unapplied
])
990 f
= file(self
.__applied
_file
, 'w+')
991 f
.writelines([line
+ '\n' for line
in applied
])
995 self
.__set
_current
(None)
997 self
.__set
_current
(applied
[-1])
999 self
.__end
_stack
_check
()
1001 def empty_patch(self
, name
):
1002 """Returns True if the patch is empty
1004 patch
= Patch(name
, self
.__patch
_dir
, self
.__refs
_dir
)
1005 bottom
= patch
.get_bottom()
1006 top
= patch
.get_top()
1010 elif git
.get_commit(top
).get_tree() \
1011 == git
.get_commit(bottom
).get_tree():
1016 def rename_patch(self
, oldname
, newname
):
1017 applied
= self
.get_applied()
1018 unapplied
= self
.get_unapplied()
1020 if oldname
== newname
:
1021 raise StackException
, '"To" name and "from" name are the same'
1023 if newname
in applied
or newname
in unapplied
:
1024 raise StackException
, 'Patch "%s" already exists' % newname
1026 if oldname
in unapplied
:
1027 Patch(oldname
, self
.__patch
_dir
, self
.__refs
_dir
).rename(newname
)
1028 unapplied
[unapplied
.index(oldname
)] = newname
1030 f
= file(self
.__unapplied
_file
, 'w+')
1031 f
.writelines([line
+ '\n' for line
in unapplied
])
1033 elif oldname
in applied
:
1034 Patch(oldname
, self
.__patch
_dir
, self
.__refs
_dir
).rename(newname
)
1035 if oldname
== self
.get_current():
1036 self
.__set
_current
(newname
)
1038 applied
[applied
.index(oldname
)] = newname
1040 f
= file(self
.__applied
_file
, 'w+')
1041 f
.writelines([line
+ '\n' for line
in applied
])
1044 raise StackException
, 'Unknown patch "%s"' % oldname
1046 def log_patch(self
, patch
, message
):
1047 """Generate a log commit for a patch
1049 top
= git
.get_commit(patch
.get_top())
1050 msg
= '%s\t%s' % (message
, top
.get_id_hash())
1052 old_log
= patch
.get_log()
1058 log
= git
.commit(message
= msg
, parents
= parents
,
1059 cache_update
= False, tree_id
= top
.get_tree(),