chiark / gitweb /
Use get-ref-list to get the commit parents
[stgit] / stgit / stack.py
1 """Basic quilt-like functionality
2 """
3
4 __copyright__ = """
5 Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
6
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.
10
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.
15
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
19 """
20
21 import sys, os
22
23 from stgit.utils import *
24 from stgit import git, basedir, templates
25 from stgit.config import config
26
27
28 # stack exception class
29 class StackException(Exception):
30     pass
31
32 class FilterUntil:
33     def __init__(self):
34         self.should_print = True
35     def __call__(self, x, until_test, prefix):
36         if until_test(x):
37             self.should_print = False
38         if self.should_print:
39             return x[0:len(prefix)] != prefix
40         return False
41
42 #
43 # Functions
44 #
45 __comment_prefix = 'STG:'
46 __patch_prefix = 'STG_PATCH:'
47
48 def __clean_comments(f):
49     """Removes lines marked for status in a commit file
50     """
51     f.seek(0)
52
53     # remove status-prefixed lines
54     lines = f.readlines()
55
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)]
59
60     # remove empty lines at the end
61     while len(lines) != 0 and lines[-1] == '\n':
62         del lines[-1]
63
64     f.seek(0); f.truncate()
65     f.writelines(lines)
66
67 def edit_file(series, line, comment, show_patch = True):
68     fname = '.stgitmsg.txt'
69     tmpl = templates.get_template('patchdescr.tmpl')
70
71     f = file(fname, 'w+')
72     if line:
73         print >> f, line
74     elif tmpl:
75         print >> f, tmpl,
76     else:
77         print >> f
78     print >> f, __comment_prefix, comment
79     print >> f, __comment_prefix, \
80           'Lines prefixed with "%s" will be automatically removed.' \
81           % __comment_prefix
82     print >> f, __comment_prefix, \
83           'Trailing empty lines will be automatically removed.'
84
85     if show_patch:
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)
89
90     #Vim modeline must be near the end.
91     print >> f, __comment_prefix, 'vi: set textwidth=75 filetype=diff nobackup:'
92     f.close()
93
94     # the editor
95     if config.has_option('stgit', 'editor'):
96         editor = config.get('stgit', 'editor')
97     elif 'EDITOR' in os.environ:
98         editor = os.environ['EDITOR']
99     else:
100         editor = 'vi'
101     editor += ' %s' % fname
102
103     print 'Invoking the editor: "%s"...' % editor,
104     sys.stdout.flush()
105     print 'done (exit code: %d)' % os.system(editor)
106
107     f = file(fname, 'r+')
108
109     __clean_comments(f)
110     f.seek(0)
111     result = f.read()
112
113     f.close()
114     os.remove(fname)
115
116     return result
117
118 #
119 # Classes
120 #
121
122 class Patch:
123     """Basic patch implementation
124     """
125     def __init__(self, name, series_dir, refs_dir):
126         self.__series_dir = series_dir
127         self.__name = name
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
132     def create(self):
133         os.mkdir(self.__dir)
134         create_empty_file(os.path.join(self.__dir, 'bottom'))
135         create_empty_file(os.path.join(self.__dir, 'top'))
136
137     def delete(self):
138         for f in os.listdir(self.__dir):
139             os.remove(os.path.join(self.__dir, f))
140         os.rmdir(self.__dir)
141         os.remove(self.__top_ref_file)
142
143     def get_name(self):
144         return self.__name
145
146     def rename(self, newname):
147         olddir = self.__dir
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)
152
153         os.rename(olddir, self.__dir)
154         os.rename(old_ref_file, self.__top_ref_file)
155
156     def __update_top_ref(self, ref):
157         write_string(self.__top_ref_file, ref)
158
159     def update_top_ref(self):
160         top = self.get_top()
161         if top:
162             self.__update_top_ref(top)
163
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)
168             if line == '':
169                 return None
170             else:
171                 return line
172         else:
173             return None
174
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):
180             os.remove(fname)
181
182     def get_old_bottom(self):
183         return self.__get_field('bottom.old')
184
185     def get_bottom(self):
186         return self.__get_field('bottom')
187
188     def set_bottom(self, value, backup = False):
189         if backup:
190             curr = self.__get_field('bottom')
191             self.__set_field('bottom.old', curr)
192         self.__set_field('bottom', value)
193
194     def get_old_top(self):
195         return self.__get_field('top.old')
196
197     def get_top(self):
198         return self.__get_field('top')
199
200     def set_top(self, value, backup = False):
201         if backup:
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)
206
207     def restore_old_boundaries(self):
208         bottom = self.__get_field('bottom.old')
209         top = self.__get_field('top.old')
210
211         if top and bottom:
212             self.__set_field('bottom', bottom)
213             self.__set_field('top', top)
214             self.__update_top_ref(top)
215             return True
216         else:
217             return False
218
219     def get_description(self):
220         return self.__get_field('description', True)
221
222     def set_description(self, line):
223         self.__set_field('description', line, True)
224
225     def get_authname(self):
226         return self.__get_field('authname')
227
228     def set_authname(self, name):
229         if not 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)
235
236     def get_authemail(self):
237         return self.__get_field('authemail')
238
239     def set_authemail(self, address):
240         if not 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)
246
247     def get_authdate(self):
248         return self.__get_field('authdate')
249
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)
254
255     def get_commname(self):
256         return self.__get_field('commname')
257
258     def set_commname(self, name):
259         if not 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)
265
266     def get_commemail(self):
267         return self.__get_field('commemail')
268
269     def set_commemail(self, address):
270         if not 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)
276
277
278 class Series:
279     """Class including the operations on series
280     """
281     def __init__(self, name = None):
282         """Takes a series name as the parameter.
283         """
284         try:
285             if name:
286                 self.__name = name
287             else:
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
292
293         self.__series_dir = os.path.join(self.__base_dir, 'patches',
294                                          self.__name)
295         self.__refs_dir = os.path.join(self.__base_dir, 'refs', 'patches',
296                                        self.__name)
297         self.__base_file = os.path.join(self.__base_dir, 'refs', 'bases',
298                                         self.__name)
299
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')
304
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
309
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()
315
316     def get_branch(self):
317         """Return the branch name for the Series object
318         """
319         return self.__name
320
321     def __set_current(self, name):
322         """Sets the topmost patch
323         """
324         if name:
325             write_string(self.__current_file, name)
326         else:
327             create_empty_file(self.__current_file)
328
329     def get_patch(self, name):
330         """Return a Patch object for the given name
331         """
332         return Patch(name, self.__patch_dir, self.__refs_dir)
333
334     def get_current(self):
335         """Return a Patch object representing the topmost patch
336         """
337         if os.path.isfile(self.__current_file):
338             name = read_string(self.__current_file)
339         else:
340             return None
341         if name == '':
342             return None
343         else:
344             return name
345
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()]
351         f.close()
352         return names
353
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()]
359         f.close()
360         return names
361
362     def get_base_file(self):
363         self.__begin_stack_check()
364         return self.__base_file
365
366     def get_protected(self):
367         return os.path.isfile(os.path.join(self.__series_dir, 'protected'))
368
369     def protect(self):
370         protect_file = os.path.join(self.__series_dir, 'protected')
371         if not os.path.isfile(protect_file):
372             create_empty_file(protect_file)
373
374     def unprotect(self):
375         protect_file = os.path.join(self.__series_dir, 'protected')
376         if os.path.isfile(protect_file):
377             os.remove(protect_file)
378
379     def get_description(self):
380         if os.path.isfile(self.__descr_file):
381             return read_string(self.__descr_file)
382         else:
383             return ''
384
385     def __patch_is_current(self, patch):
386         return patch.get_name() == read_string(self.__current_file)
387
388     def __patch_applied(self, name):
389         """Return true if the patch exists in the applied list
390         """
391         return name in self.get_applied()
392
393     def __patch_unapplied(self, name):
394         """Return true if the patch exists in the unapplied list
395         """
396         return name in self.get_unapplied()
397
398     def __begin_stack_check(self):
399         """Save the current HEAD into .git/refs/heads/base if the stack
400         is empty
401         """
402         if len(self.get_applied()) == 0:
403             head = git.get_head()
404             write_string(self.__base_file, head)
405
406     def __end_stack_check(self):
407         """Remove .git/refs/heads/base if the stack is empty.
408         This warning should never happen
409         """
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'
413
414     def head_top_equal(self):
415         """Return true if the head and the top are the same
416         """
417         crt = self.get_current()
418         if not crt:
419             # we don't care, no patches applied
420             return True
421         return git.get_head() == Patch(crt, self.__patch_dir,
422                                        self.__refs_dir).get_top()
423
424     def is_initialised(self):
425         """Checks if series is already initialised
426         """
427         return os.path.isdir(self.__patch_dir)
428
429     def init(self, create_at=False):
430         """Initialises the stgit series
431         """
432         bases_dir = os.path.join(self.__base_dir, 'refs', 'bases')
433
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'
440
441         if (create_at!=False):
442             git.create_branch(self.__name, create_at)
443
444         os.makedirs(self.__patch_dir)
445
446         create_dirs(bases_dir)
447
448         create_empty_file(self.__applied_file)
449         create_empty_file(self.__unapplied_file)
450         create_empty_file(self.__descr_file)
451         os.makedirs(os.path.join(self.__series_dir, 'patches'))
452         os.makedirs(self.__refs_dir)
453         self.__begin_stack_check()
454
455     def convert(self):
456         """Either convert to use a separate patch directory, or
457         unconvert to place the patches in the same directory with
458         series control files
459         """
460         if self.__patch_dir == self.__series_dir:
461             print 'Converting old-style to new-style...',
462             sys.stdout.flush()
463
464             self.__patch_dir = os.path.join(self.__series_dir, 'patches')
465             os.makedirs(self.__patch_dir)
466
467             for p in self.get_applied() + self.get_unapplied():
468                 src = os.path.join(self.__series_dir, p)
469                 dest = os.path.join(self.__patch_dir, p)
470                 os.rename(src, dest)
471
472             print 'done'
473
474         else:
475             print 'Converting new-style to old-style...',
476             sys.stdout.flush()
477
478             for p in self.get_applied() + self.get_unapplied():
479                 src = os.path.join(self.__patch_dir, p)
480                 dest = os.path.join(self.__series_dir, p)
481                 os.rename(src, dest)
482
483             if not os.listdir(self.__patch_dir):
484                 os.rmdir(self.__patch_dir)
485                 print 'done'
486             else:
487                 print 'Patch directory %s is not empty.' % self.__name
488
489             self.__patch_dir = self.__series_dir
490
491     def rename(self, to_name):
492         """Renames a series
493         """
494         to_stack = Series(to_name)
495
496         if to_stack.is_initialised():
497             raise StackException, '"%s" already exists' % to_stack.get_branch()
498         if os.path.exists(to_stack.__base_file):
499             os.remove(to_stack.__base_file)
500
501         git.rename_branch(self.__name, to_name)
502
503         if os.path.isdir(self.__series_dir):
504             rename(os.path.join(self.__base_dir, 'patches'),
505                    self.__name, to_stack.__name)
506         if os.path.exists(self.__base_file):
507             rename(os.path.join(self.__base_dir, 'refs', 'bases'),
508                    self.__name, to_stack.__name)
509         if os.path.exists(self.__refs_dir):
510             rename(os.path.join(self.__base_dir, 'refs', 'patches'),
511                    self.__name, to_stack.__name)
512
513         self.__init__(to_name)
514
515     def clone(self, target_series):
516         """Clones a series
517         """
518         base = read_string(self.get_base_file())
519         Series(target_series).init(create_at = base)
520         new_series = Series(target_series)
521
522         # generate an artificial description file
523         write_string(new_series.__descr_file, 'clone of "%s"' % self.__name)
524
525         # clone self's entire series as unapplied patches
526         patches = self.get_applied() + self.get_unapplied()
527         patches.reverse()
528         for p in patches:
529             patch = self.get_patch(p)
530             new_series.new_patch(p, message = patch.get_description(),
531                                  can_edit = False, unapplied = True,
532                                  bottom = patch.get_bottom(),
533                                  top = patch.get_top(),
534                                  author_name = patch.get_authname(),
535                                  author_email = patch.get_authemail(),
536                                  author_date = patch.get_authdate())
537
538         # fast forward the cloned series to self's top
539         new_series.forward_patches(self.get_applied())
540
541     def delete(self, force = False):
542         """Deletes an stgit series
543         """
544         if self.is_initialised():
545             patches = self.get_unapplied() + self.get_applied()
546             if not force and patches:
547                 raise StackException, \
548                       'Cannot delete: the series still contains patches'
549             for p in patches:
550                 Patch(p, self.__patch_dir, self.__refs_dir).delete()
551
552             if os.path.exists(self.__applied_file):
553                 os.remove(self.__applied_file)
554             if os.path.exists(self.__unapplied_file):
555                 os.remove(self.__unapplied_file)
556             if os.path.exists(self.__current_file):
557                 os.remove(self.__current_file)
558             if os.path.exists(self.__descr_file):
559                 os.remove(self.__descr_file)
560             if not os.listdir(self.__patch_dir):
561                 os.rmdir(self.__patch_dir)
562             else:
563                 print 'Patch directory %s is not empty.' % self.__name
564             if not os.listdir(self.__series_dir):
565                 remove_dirs(os.path.join(self.__base_dir, 'patches'),
566                             self.__name)
567             else:
568                 print 'Series directory %s is not empty.' % self.__name
569             if not os.listdir(self.__refs_dir):
570                 remove_dirs(os.path.join(self.__base_dir, 'refs', 'patches'),
571                             self.__name)
572             else:
573                 print 'Refs directory %s is not empty.' % self.__refs_dir
574
575         if os.path.exists(self.__base_file):
576             remove_file_and_dirs(
577                 os.path.join(self.__base_dir, 'refs', 'bases'), self.__name)
578
579     def refresh_patch(self, files = None, message = None, edit = False,
580                       show_patch = False,
581                       cache_update = True,
582                       author_name = None, author_email = None,
583                       author_date = None,
584                       committer_name = None, committer_email = None,
585                       backup = False):
586         """Generates a new commit for the given patch
587         """
588         name = self.get_current()
589         if not name:
590             raise StackException, 'No patches applied'
591
592         patch = Patch(name, self.__patch_dir, self.__refs_dir)
593
594         descr = patch.get_description()
595         if not (message or descr):
596             edit = True
597             descr = ''
598         elif message:
599             descr = message
600
601         if not message and edit:
602             descr = edit_file(self, descr.rstrip(), \
603                               'Please edit the description for patch "%s" ' \
604                               'above.' % name, show_patch)
605
606         if not author_name:
607             author_name = patch.get_authname()
608         if not author_email:
609             author_email = patch.get_authemail()
610         if not author_date:
611             author_date = patch.get_authdate()
612         if not committer_name:
613             committer_name = patch.get_commname()
614         if not committer_email:
615             committer_email = patch.get_commemail()
616
617         bottom = patch.get_bottom()
618
619         commit_id = git.commit(files = files,
620                                message = descr, parents = [bottom],
621                                cache_update = cache_update,
622                                allowempty = True,
623                                author_name = author_name,
624                                author_email = author_email,
625                                author_date = author_date,
626                                committer_name = committer_name,
627                                committer_email = committer_email)
628
629         patch.set_bottom(bottom, backup = backup)
630         patch.set_top(commit_id, backup = backup)
631         patch.set_description(descr)
632         patch.set_authname(author_name)
633         patch.set_authemail(author_email)
634         patch.set_authdate(author_date)
635         patch.set_commname(committer_name)
636         patch.set_commemail(committer_email)
637
638         return commit_id
639
640     def undo_refresh(self):
641         """Undo the patch boundaries changes caused by 'refresh'
642         """
643         name = self.get_current()
644         assert(name)
645
646         patch = Patch(name, self.__patch_dir, self.__refs_dir)
647         old_bottom = patch.get_old_bottom()
648         old_top = patch.get_old_top()
649
650         # the bottom of the patch is not changed by refresh. If the
651         # old_bottom is different, there wasn't any previous 'refresh'
652         # command (probably only a 'push')
653         if old_bottom != patch.get_bottom() or old_top == patch.get_top():
654             raise StackException, 'No refresh undo information available'
655
656         git.reset(tree_id = old_top, check_out = False)
657         patch.restore_old_boundaries()
658
659     def new_patch(self, name, message = None, can_edit = True,
660                   unapplied = False, show_patch = False,
661                   top = None, bottom = None,
662                   author_name = None, author_email = None, author_date = None,
663                   committer_name = None, committer_email = None,
664                   before_existing = False):
665         """Creates a new patch
666         """
667         if self.__patch_applied(name) or self.__patch_unapplied(name):
668             raise StackException, 'Patch "%s" already exists' % name
669
670         if not message and can_edit:
671             descr = edit_file(self, None, \
672                               'Please enter the description for patch "%s" ' \
673                               'above.' % name, show_patch)
674         else:
675             descr = message
676
677         head = git.get_head()
678
679         self.__begin_stack_check()
680
681         patch = Patch(name, self.__patch_dir, self.__refs_dir)
682         patch.create()
683
684         if bottom:
685             patch.set_bottom(bottom)
686         else:
687             patch.set_bottom(head)
688         if top:
689             patch.set_top(top)
690         else:
691             patch.set_top(head)
692
693         patch.set_description(descr)
694         patch.set_authname(author_name)
695         patch.set_authemail(author_email)
696         patch.set_authdate(author_date)
697         patch.set_commname(committer_name)
698         patch.set_commemail(committer_email)
699
700         if unapplied:
701             patches = [patch.get_name()] + self.get_unapplied()
702
703             f = file(self.__unapplied_file, 'w+')
704             f.writelines([line + '\n' for line in patches])
705             f.close()
706         else:
707             if before_existing:
708                 insert_string(self.__applied_file, patch.get_name())
709                 if not self.get_current():
710                     self.__set_current(name)
711             else:
712                 append_string(self.__applied_file, patch.get_name())
713                 self.__set_current(name)
714
715                 self.refresh_patch(cache_update = False)
716
717     def delete_patch(self, name):
718         """Deletes a patch
719         """
720         patch = Patch(name, self.__patch_dir, self.__refs_dir)
721
722         if self.__patch_is_current(patch):
723             self.pop_patch(name)
724         elif self.__patch_applied(name):
725             raise StackException, 'Cannot remove an applied patch, "%s", ' \
726                   'which is not current' % name
727         elif not name in self.get_unapplied():
728             raise StackException, 'Unknown patch "%s"' % name
729
730         patch.delete()
731
732         unapplied = self.get_unapplied()
733         unapplied.remove(name)
734         f = file(self.__unapplied_file, 'w+')
735         f.writelines([line + '\n' for line in unapplied])
736         f.close()
737         self.__begin_stack_check()
738
739     def forward_patches(self, names):
740         """Try to fast-forward an array of patches.
741
742         On return, patches in names[0:returned_value] have been pushed on the
743         stack. Apply the rest with push_patch
744         """
745         unapplied = self.get_unapplied()
746         self.__begin_stack_check()
747
748         forwarded = 0
749         top = git.get_head()
750
751         for name in names:
752             assert(name in unapplied)
753
754             patch = Patch(name, self.__patch_dir, self.__refs_dir)
755
756             head = top
757             bottom = patch.get_bottom()
758             top = patch.get_top()
759
760             # top != bottom always since we have a commit for each patch
761             if head == bottom:
762                 # reset the backup information
763                 patch.set_bottom(head, backup = True)
764                 patch.set_top(top, backup = True)
765
766             else:
767                 head_tree = git.get_commit(head).get_tree()
768                 bottom_tree = git.get_commit(bottom).get_tree()
769                 if head_tree == bottom_tree:
770                     # We must just reparent this patch and create a new commit
771                     # for it
772                     descr = patch.get_description()
773                     author_name = patch.get_authname()
774                     author_email = patch.get_authemail()
775                     author_date = patch.get_authdate()
776                     committer_name = patch.get_commname()
777                     committer_email = patch.get_commemail()
778
779                     top_tree = git.get_commit(top).get_tree()
780
781                     top = git.commit(message = descr, parents = [head],
782                                      cache_update = False,
783                                      tree_id = top_tree,
784                                      allowempty = True,
785                                      author_name = author_name,
786                                      author_email = author_email,
787                                      author_date = author_date,
788                                      committer_name = committer_name,
789                                      committer_email = committer_email)
790
791                     patch.set_bottom(head, backup = True)
792                     patch.set_top(top, backup = True)
793                 else:
794                     top = head
795                     # stop the fast-forwarding, must do a real merge
796                     break
797
798             forwarded+=1
799             unapplied.remove(name)
800
801         if forwarded == 0:
802             return 0
803
804         git.switch(top)
805
806         append_strings(self.__applied_file, names[0:forwarded])
807
808         f = file(self.__unapplied_file, 'w+')
809         f.writelines([line + '\n' for line in unapplied])
810         f.close()
811
812         self.__set_current(name)
813
814         return forwarded
815
816     def merged_patches(self, names):
817         """Test which patches were merged upstream by reverse-applying
818         them in reverse order. The function returns the list of
819         patches detected to have been applied. The state of the tree
820         is restored to the original one
821         """
822         patches = [Patch(name, self.__patch_dir, self.__refs_dir)
823                    for name in names]
824         patches.reverse()
825
826         merged = []
827         for p in patches:
828             if git.apply_diff(p.get_top(), p.get_bottom()):
829                 merged.append(p.get_name())
830         merged.reverse()
831
832         git.reset()
833
834         return merged
835
836     def push_patch(self, name, empty = False):
837         """Pushes a patch on the stack
838         """
839         unapplied = self.get_unapplied()
840         assert(name in unapplied)
841
842         self.__begin_stack_check()
843
844         patch = Patch(name, self.__patch_dir, self.__refs_dir)
845
846         head = git.get_head()
847         bottom = patch.get_bottom()
848         top = patch.get_top()
849
850         ex = None
851         modified = False
852
853         # top != bottom always since we have a commit for each patch
854         if empty:
855             # just make an empty patch (top = bottom = HEAD). This
856             # option is useful to allow undoing already merged
857             # patches. The top is updated by refresh_patch since we
858             # need an empty commit
859             patch.set_bottom(head, backup = True)
860             patch.set_top(head, backup = True)
861             modified = True
862         elif head == bottom:
863             # reset the backup information
864             patch.set_bottom(bottom, backup = True)
865             patch.set_top(top, backup = True)
866
867             git.switch(top)
868         else:
869             # new patch needs to be refreshed.
870             # The current patch is empty after merge.
871             patch.set_bottom(head, backup = True)
872             patch.set_top(head, backup = True)
873
874             # Try the fast applying first. If this fails, fall back to the
875             # three-way merge
876             if not git.apply_diff(bottom, top):
877                 # if git.apply_diff() fails, the patch requires a diff3
878                 # merge and can be reported as modified
879                 modified = True
880
881                 # merge can fail but the patch needs to be pushed
882                 try:
883                     git.merge(bottom, head, top)
884                 except git.GitException, ex:
885                     print >> sys.stderr, \
886                           'The merge failed during "push". ' \
887                           'Use "refresh" after fixing the conflicts'
888
889         append_string(self.__applied_file, name)
890
891         unapplied.remove(name)
892         f = file(self.__unapplied_file, 'w+')
893         f.writelines([line + '\n' for line in unapplied])
894         f.close()
895
896         self.__set_current(name)
897
898         # head == bottom case doesn't need to refresh the patch
899         if empty or head != bottom:
900             if not ex:
901                 # if the merge was OK and no conflicts, just refresh the patch
902                 # The GIT cache was already updated by the merge operation
903                 self.refresh_patch(cache_update = False)
904             else:
905                 raise StackException, str(ex)
906
907         return modified
908
909     def undo_push(self):
910         name = self.get_current()
911         assert(name)
912
913         patch = Patch(name, self.__patch_dir, self.__refs_dir)
914         old_bottom = patch.get_old_bottom()
915         old_top = patch.get_old_top()
916
917         # the top of the patch is changed by a push operation only
918         # together with the bottom (otherwise the top was probably
919         # modified by 'refresh'). If they are both unchanged, there
920         # was a fast forward
921         if old_bottom == patch.get_bottom() and old_top != patch.get_top():
922             raise StackException, 'No push undo information available'
923
924         git.reset()
925         self.pop_patch(name)
926         return patch.restore_old_boundaries()
927
928     def pop_patch(self, name):
929         """Pops the top patch from the stack
930         """
931         applied = self.get_applied()
932         applied.reverse()
933         assert(name in applied)
934
935         patch = Patch(name, self.__patch_dir, self.__refs_dir)
936
937         git.switch(patch.get_bottom())
938
939         # save the new applied list
940         idx = applied.index(name) + 1
941
942         popped = applied[:idx]
943         popped.reverse()
944         unapplied = popped + self.get_unapplied()
945
946         f = file(self.__unapplied_file, 'w+')
947         f.writelines([line + '\n' for line in unapplied])
948         f.close()
949
950         del applied[:idx]
951         applied.reverse()
952
953         f = file(self.__applied_file, 'w+')
954         f.writelines([line + '\n' for line in applied])
955         f.close()
956
957         if applied == []:
958             self.__set_current(None)
959         else:
960             self.__set_current(applied[-1])
961
962         self.__end_stack_check()
963
964     def empty_patch(self, name):
965         """Returns True if the patch is empty
966         """
967         patch = Patch(name, self.__patch_dir, self.__refs_dir)
968         bottom = patch.get_bottom()
969         top = patch.get_top()
970
971         if bottom == top:
972             return True
973         elif git.get_commit(top).get_tree() \
974                  == git.get_commit(bottom).get_tree():
975             return True
976
977         return False
978
979     def rename_patch(self, oldname, newname):
980         applied = self.get_applied()
981         unapplied = self.get_unapplied()
982
983         if oldname == newname:
984             raise StackException, '"To" name and "from" name are the same'
985
986         if newname in applied or newname in unapplied:
987             raise StackException, 'Patch "%s" already exists' % newname
988
989         if oldname in unapplied:
990             Patch(oldname, self.__patch_dir, self.__refs_dir).rename(newname)
991             unapplied[unapplied.index(oldname)] = newname
992
993             f = file(self.__unapplied_file, 'w+')
994             f.writelines([line + '\n' for line in unapplied])
995             f.close()
996         elif oldname in applied:
997             Patch(oldname, self.__patch_dir, self.__refs_dir).rename(newname)
998             if oldname == self.get_current():
999                 self.__set_current(newname)
1000
1001             applied[applied.index(oldname)] = newname
1002
1003             f = file(self.__applied_file, 'w+')
1004             f.writelines([line + '\n' for line in applied])
1005             f.close()
1006         else:
1007             raise StackException, 'Unknown patch "%s"' % oldname