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 *
25 from stgit.config import config
28 # stack exception class
29 class StackException(Exception):
34 self.should_print = True
35 def __call__(self, x, until_test, prefix):
37 self.should_print = False
39 return x[0:len(prefix)] != prefix
45 __comment_prefix = 'STG:'
46 __patch_prefix = 'STG_PATCH:'
48 def __clean_comments(f):
49 """Removes lines marked for status in a commit file
53 # remove status-prefixed lines
56 patch_filter = FilterUntil()
57 until_test = lambda t: t == (__patch_prefix + '\n')
58 lines = [l for l in lines if patch_filter(l, until_test, __comment_prefix)]
60 # remove empty lines at the end
61 while len(lines) != 0 and lines[-1] == '\n':
64 f.seek(0); f.truncate()
67 def edit_file(series, line, comment, show_patch = True):
69 tmpl = os.path.join(git.get_base_dir(), 'patchdescr.tmpl')
74 elif os.path.isfile(tmpl):
75 print >> f, file(tmpl).read().rstrip()
78 print >> f, __comment_prefix, comment
79 print >> f, __comment_prefix, \
80 'Lines prefixed with "%s" will be automatically removed.' \
82 print >> f, __comment_prefix, \
83 'Trailing empty lines will be automatically removed.'
86 print >> f, __patch_prefix
87 # series.get_patch(series.get_current()).get_top()
88 git.diff([], series.get_patch(series.get_current()).get_bottom(), None, f)
90 #Vim modeline must be near the end.
91 print >> f, __comment_prefix, 'vi: set textwidth=75 filetype=diff nobackup:'
95 if config.has_option('stgit', 'editor'):
96 editor = config.get('stgit', 'editor')
97 elif 'EDITOR' in os.environ:
98 editor = os.environ['EDITOR']
101 editor += ' %s' % fname
103 print 'Invoking the editor: "%s"...' % editor,
105 print 'done (exit code: %d)' % os.system(editor)
107 f = file(fname, 'r+')
123 """Basic patch implementation
125 def __init__(self, name, series_dir, refs_dir):
126 self.__series_dir = series_dir
128 self.__dir = os.path.join(self.__series_dir, self.__name)
129 self.__refs_dir = refs_dir
130 self.__top_ref_file = os.path.join(self.__refs_dir, self.__name)
134 create_empty_file(os.path.join(self.__dir, 'bottom'))
135 create_empty_file(os.path.join(self.__dir, 'top'))
138 for f in os.listdir(self.__dir):
139 os.remove(os.path.join(self.__dir, f))
141 os.remove(self.__top_ref_file)
146 def rename(self, newname):
148 old_ref_file = self.__top_ref_file
149 self.__name = newname
150 self.__dir = os.path.join(self.__series_dir, self.__name)
151 self.__top_ref_file = os.path.join(self.__refs_dir, self.__name)
153 os.rename(olddir, self.__dir)
154 os.rename(old_ref_file, self.__top_ref_file)
156 def __update_top_ref(self, ref):
157 write_string(self.__top_ref_file, ref)
159 def update_top_ref(self):
162 self.__update_top_ref(top)
164 def __get_field(self, name, multiline = False):
165 id_file = os.path.join(self.__dir, name)
166 if os.path.isfile(id_file):
167 line = read_string(id_file, multiline)
175 def __set_field(self, name, value, multiline = False):
176 fname = os.path.join(self.__dir, name)
177 if value and value != '':
178 write_string(fname, value, multiline)
179 elif os.path.isfile(fname):
182 def get_old_bottom(self):
183 return self.__get_field('bottom.old')
185 def get_bottom(self):
186 return self.__get_field('bottom')
188 def set_bottom(self, value, backup = False):
190 curr = self.__get_field('bottom')
192 self.__set_field('bottom.old', curr)
194 self.__set_field('bottom.old', None)
195 self.__set_field('bottom', value)
197 def get_old_top(self):
198 return self.__get_field('top.old')
201 return self.__get_field('top')
203 def set_top(self, value, backup = False):
205 curr = self.__get_field('top')
207 self.__set_field('top.old', curr)
209 self.__set_field('top.old', None)
210 self.__set_field('top', value)
211 self.__update_top_ref(value)
213 def restore_old_boundaries(self):
214 bottom = self.__get_field('bottom.old')
215 top = self.__get_field('top.old')
218 self.__set_field('bottom', bottom)
219 self.__set_field('top', top)
220 self.__update_top_ref(top)
225 def get_description(self):
226 return self.__get_field('description', True)
228 def set_description(self, line):
229 self.__set_field('description', line, True)
231 def get_authname(self):
232 return self.__get_field('authname')
234 def set_authname(self, name):
236 if config.has_option('stgit', 'authname'):
237 name = config.get('stgit', 'authname')
238 elif 'GIT_AUTHOR_NAME' in os.environ:
239 name = os.environ['GIT_AUTHOR_NAME']
240 self.__set_field('authname', name)
242 def get_authemail(self):
243 return self.__get_field('authemail')
245 def set_authemail(self, address):
247 if config.has_option('stgit', 'authemail'):
248 address = config.get('stgit', 'authemail')
249 elif 'GIT_AUTHOR_EMAIL' in os.environ:
250 address = os.environ['GIT_AUTHOR_EMAIL']
251 self.__set_field('authemail', address)
253 def get_authdate(self):
254 return self.__get_field('authdate')
256 def set_authdate(self, date):
257 if not date and 'GIT_AUTHOR_DATE' in os.environ:
258 date = os.environ['GIT_AUTHOR_DATE']
259 self.__set_field('authdate', date)
261 def get_commname(self):
262 return self.__get_field('commname')
264 def set_commname(self, name):
266 if config.has_option('stgit', 'commname'):
267 name = config.get('stgit', 'commname')
268 elif 'GIT_COMMITTER_NAME' in os.environ:
269 name = os.environ['GIT_COMMITTER_NAME']
270 self.__set_field('commname', name)
272 def get_commemail(self):
273 return self.__get_field('commemail')
275 def set_commemail(self, address):
277 if config.has_option('stgit', 'commemail'):
278 address = config.get('stgit', 'commemail')
279 elif 'GIT_COMMITTER_EMAIL' in os.environ:
280 address = os.environ['GIT_COMMITTER_EMAIL']
281 self.__set_field('commemail', address)
285 """Class including the operations on series
287 def __init__(self, name = None):
288 """Takes a series name as the parameter.
294 self.__name = git.get_head_file()
295 self.__base_dir = git.get_base_dir()
296 except git.GitException, ex:
297 raise StackException, 'GIT tree not initialised: %s' % ex
299 self.__series_dir = os.path.join(self.__base_dir, 'patches',
301 self.__refs_dir = os.path.join(self.__base_dir, 'refs', 'patches',
303 self.__base_file = os.path.join(self.__base_dir, 'refs', 'bases',
306 self.__applied_file = os.path.join(self.__series_dir, 'applied')
307 self.__unapplied_file = os.path.join(self.__series_dir, 'unapplied')
308 self.__current_file = os.path.join(self.__series_dir, 'current')
309 self.__descr_file = os.path.join(self.__series_dir, 'description')
311 # where this series keeps its patches
312 self.__patch_dir = os.path.join(self.__series_dir, 'patches')
313 if not os.path.isdir(self.__patch_dir):
314 self.__patch_dir = self.__series_dir
316 # if no __refs_dir, create and populate it (upgrade old repositories)
317 if self.is_initialised() and not os.path.isdir(self.__refs_dir):
318 os.makedirs(self.__refs_dir)
319 for patch in self.get_applied() + self.get_unapplied():
320 self.get_patch(patch).update_top_ref()
322 def get_branch(self):
323 """Return the branch name for the Series object
327 def __set_current(self, name):
328 """Sets the topmost patch
331 write_string(self.__current_file, name)
333 create_empty_file(self.__current_file)
335 def get_patch(self, name):
336 """Return a Patch object for the given name
338 return Patch(name, self.__patch_dir, self.__refs_dir)
340 def get_current(self):
341 """Return a Patch object representing the topmost patch
343 if os.path.isfile(self.__current_file):
344 name = read_string(self.__current_file)
352 def get_applied(self):
353 if not os.path.isfile(self.__applied_file):
354 raise StackException, 'Branch "%s" not initialised' % self.__name
355 f = file(self.__applied_file)
356 names = [line.strip() for line in f.readlines()]
360 def get_unapplied(self):
361 if not os.path.isfile(self.__unapplied_file):
362 raise StackException, 'Branch "%s" not initialised' % self.__name
363 f = file(self.__unapplied_file)
364 names = [line.strip() for line in f.readlines()]
368 def get_base_file(self):
369 self.__begin_stack_check()
370 return self.__base_file
372 def get_protected(self):
373 return os.path.isfile(os.path.join(self.__series_dir, 'protected'))
376 protect_file = os.path.join(self.__series_dir, 'protected')
377 if not os.path.isfile(protect_file):
378 create_empty_file(protect_file)
381 protect_file = os.path.join(self.__series_dir, 'protected')
382 if os.path.isfile(protect_file):
383 os.remove(protect_file)
385 def get_description(self):
386 if os.path.isfile(self.__descr_file):
387 return read_string(self.__descr_file)
391 def __patch_is_current(self, patch):
392 return patch.get_name() == read_string(self.__current_file)
394 def __patch_applied(self, name):
395 """Return true if the patch exists in the applied list
397 return name in self.get_applied()
399 def __patch_unapplied(self, name):
400 """Return true if the patch exists in the unapplied list
402 return name in self.get_unapplied()
404 def __begin_stack_check(self):
405 """Save the current HEAD into .git/refs/heads/base if the stack
408 if len(self.get_applied()) == 0:
409 head = git.get_head()
410 write_string(self.__base_file, head)
412 def __end_stack_check(self):
413 """Remove .git/refs/heads/base if the stack is empty.
414 This warning should never happen
416 if len(self.get_applied()) == 0 \
417 and read_string(self.__base_file) != git.get_head():
418 print 'Warning: stack empty but the HEAD and base are different'
420 def head_top_equal(self):
421 """Return true if the head and the top are the same
423 crt = self.get_current()
425 # we don't care, no patches applied
427 return git.get_head() == Patch(crt, self.__patch_dir,
428 self.__refs_dir).get_top()
430 def is_initialised(self):
431 """Checks if series is already initialised
433 return os.path.isdir(self.__patch_dir)
436 """Initialises the stgit series
438 bases_dir = os.path.join(self.__base_dir, 'refs', 'bases')
440 if self.is_initialised():
441 raise StackException, self.__patch_dir + ' already exists'
442 os.makedirs(self.__patch_dir)
444 if not os.path.isdir(bases_dir):
445 os.makedirs(bases_dir)
447 create_empty_file(self.__applied_file)
448 create_empty_file(self.__unapplied_file)
449 create_empty_file(self.__descr_file)
450 os.makedirs(os.path.join(self.__series_dir, 'patches'))
451 os.makedirs(self.__refs_dir)
452 self.__begin_stack_check()
455 """Either convert to use a separate patch directory, or
456 unconvert to place the patches in the same directory with
459 if self.__patch_dir == self.__series_dir:
460 print 'Converting old-style to new-style...',
463 self.__patch_dir = os.path.join(self.__series_dir, 'patches')
464 os.makedirs(self.__patch_dir)
466 for p in self.get_applied() + self.get_unapplied():
467 src = os.path.join(self.__series_dir, p)
468 dest = os.path.join(self.__patch_dir, p)
474 print 'Converting new-style to old-style...',
477 for p in self.get_applied() + self.get_unapplied():
478 src = os.path.join(self.__patch_dir, p)
479 dest = os.path.join(self.__series_dir, p)
482 if not os.listdir(self.__patch_dir):
483 os.rmdir(self.__patch_dir)
486 print 'Patch directory %s is not empty.' % self.__name
488 self.__patch_dir = self.__series_dir
490 def rename(self, to_name):
493 to_stack = Series(to_name)
495 if to_stack.is_initialised():
496 raise StackException, '"%s" already exists' % to_stack.get_branch()
497 if os.path.exists(to_stack.__base_file):
498 os.remove(to_stack.__base_file)
500 git.rename_branch(self.__name, to_name)
502 if os.path.isdir(self.__series_dir):
503 os.rename(self.__series_dir, to_stack.__series_dir)
504 if os.path.exists(self.__base_file):
505 os.rename(self.__base_file, to_stack.__base_file)
507 self.__init__(to_name)
509 def clone(self, target_series):
512 base = read_string(self.get_base_file())
513 git.create_branch(target_series, tree_id = base)
514 Series(target_series).init()
515 new_series = Series(target_series)
517 # generate an artificial description file
518 write_string(new_series.__descr_file, 'clone of "%s"' % self.__name)
520 # clone self's entire series as unapplied patches
521 patches = self.get_applied() + self.get_unapplied()
524 patch = self.get_patch(p)
525 new_series.new_patch(p, message = patch.get_description(),
526 can_edit = False, unapplied = True,
527 bottom = patch.get_bottom(),
528 top = patch.get_top(),
529 author_name = patch.get_authname(),
530 author_email = patch.get_authemail(),
531 author_date = patch.get_authdate())
533 # fast forward the cloned series to self's top
534 new_series.forward_patches(self.get_applied())
536 def delete(self, force = False):
537 """Deletes an stgit series
539 if self.is_initialised():
540 patches = self.get_unapplied() + self.get_applied()
541 if not force and patches:
542 raise StackException, \
543 'Cannot delete: the series still contains patches'
545 Patch(p, self.__patch_dir, self.__refs_dir).delete()
547 if os.path.exists(self.__applied_file):
548 os.remove(self.__applied_file)
549 if os.path.exists(self.__unapplied_file):
550 os.remove(self.__unapplied_file)
551 if os.path.exists(self.__current_file):
552 os.remove(self.__current_file)
553 if os.path.exists(self.__descr_file):
554 os.remove(self.__descr_file)
555 if not os.listdir(self.__patch_dir):
556 os.rmdir(self.__patch_dir)
558 print 'Patch directory %s is not empty.' % self.__name
559 if not os.listdir(self.__series_dir):
560 os.rmdir(self.__series_dir)
562 print 'Series directory %s is not empty.' % self.__name
563 if not os.listdir(self.__refs_dir):
564 os.rmdir(self.__refs_dir)
566 print 'Refs directory %s is not empty.' % self.__refs_dir
568 if os.path.exists(self.__base_file):
569 os.remove(self.__base_file)
571 def refresh_patch(self, files = None, message = None, edit = False,
574 author_name = None, author_email = None,
576 committer_name = None, committer_email = None):
577 """Generates a new commit for the given patch
579 name = self.get_current()
581 raise StackException, 'No patches applied'
583 patch = Patch(name, self.__patch_dir, self.__refs_dir)
585 descr = patch.get_description()
586 if not (message or descr):
592 if not message and edit:
593 descr = edit_file(self, descr.rstrip(), \
594 'Please edit the description for patch "%s" ' \
595 'above.' % name, show_patch)
598 author_name = patch.get_authname()
600 author_email = patch.get_authemail()
602 author_date = patch.get_authdate()
603 if not committer_name:
604 committer_name = patch.get_commname()
605 if not committer_email:
606 committer_email = patch.get_commemail()
608 commit_id = git.commit(files = files,
609 message = descr, parents = [patch.get_bottom()],
610 cache_update = cache_update,
612 author_name = author_name,
613 author_email = author_email,
614 author_date = author_date,
615 committer_name = committer_name,
616 committer_email = committer_email)
618 patch.set_top(commit_id)
619 patch.set_description(descr)
620 patch.set_authname(author_name)
621 patch.set_authemail(author_email)
622 patch.set_authdate(author_date)
623 patch.set_commname(committer_name)
624 patch.set_commemail(committer_email)
628 def new_patch(self, name, message = None, can_edit = True,
629 unapplied = False, show_patch = False,
630 top = None, bottom = None,
631 author_name = None, author_email = None, author_date = None,
632 committer_name = None, committer_email = None,
633 before_existing = False):
634 """Creates a new patch
636 if self.__patch_applied(name) or self.__patch_unapplied(name):
637 raise StackException, 'Patch "%s" already exists' % name
639 if not message and can_edit:
640 descr = edit_file(self, None, \
641 'Please enter the description for patch "%s" ' \
642 'above.' % name, show_patch)
646 head = git.get_head()
648 self.__begin_stack_check()
650 patch = Patch(name, self.__patch_dir, self.__refs_dir)
654 patch.set_bottom(bottom)
656 patch.set_bottom(head)
662 patch.set_description(descr)
663 patch.set_authname(author_name)
664 patch.set_authemail(author_email)
665 patch.set_authdate(author_date)
666 patch.set_commname(committer_name)
667 patch.set_commemail(committer_email)
670 patches = [patch.get_name()] + self.get_unapplied()
672 f = file(self.__unapplied_file, 'w+')
673 f.writelines([line + '\n' for line in patches])
677 insert_string(self.__applied_file, patch.get_name())
678 if not self.get_current():
679 self.__set_current(name)
681 append_string(self.__applied_file, patch.get_name())
682 self.__set_current(name)
684 def delete_patch(self, name):
687 patch = Patch(name, self.__patch_dir, self.__refs_dir)
689 if self.__patch_is_current(patch):
691 elif self.__patch_applied(name):
692 raise StackException, 'Cannot remove an applied patch, "%s", ' \
693 'which is not current' % name
694 elif not name in self.get_unapplied():
695 raise StackException, 'Unknown patch "%s"' % name
699 unapplied = self.get_unapplied()
700 unapplied.remove(name)
701 f = file(self.__unapplied_file, 'w+')
702 f.writelines([line + '\n' for line in unapplied])
704 self.__begin_stack_check()
706 def forward_patches(self, names):
707 """Try to fast-forward an array of patches.
709 On return, patches in names[0:returned_value] have been pushed on the
710 stack. Apply the rest with push_patch
712 unapplied = self.get_unapplied()
713 self.__begin_stack_check()
719 assert(name in unapplied)
721 patch = Patch(name, self.__patch_dir, self.__refs_dir)
724 bottom = patch.get_bottom()
725 top = patch.get_top()
727 # top != bottom always since we have a commit for each patch
729 # reset the backup information
730 patch.set_bottom(head, backup = True)
731 patch.set_top(top, backup = True)
734 head_tree = git.get_commit(head).get_tree()
735 bottom_tree = git.get_commit(bottom).get_tree()
736 if head_tree == bottom_tree:
737 # We must just reparent this patch and create a new commit
739 descr = patch.get_description()
740 author_name = patch.get_authname()
741 author_email = patch.get_authemail()
742 author_date = patch.get_authdate()
743 committer_name = patch.get_commname()
744 committer_email = patch.get_commemail()
746 top_tree = git.get_commit(top).get_tree()
748 top = git.commit(message = descr, parents = [head],
749 cache_update = False,
752 author_name = author_name,
753 author_email = author_email,
754 author_date = author_date,
755 committer_name = committer_name,
756 committer_email = committer_email)
758 patch.set_bottom(head, backup = True)
759 patch.set_top(top, backup = True)
762 # stop the fast-forwarding, must do a real merge
766 unapplied.remove(name)
773 append_strings(self.__applied_file, names[0:forwarded])
775 f = file(self.__unapplied_file, 'w+')
776 f.writelines([line + '\n' for line in unapplied])
779 self.__set_current(name)
783 def merged_patches(self, names):
784 """Test which patches were merged upstream by reverse-applying
785 them in reverse order. The function returns the list of
786 patches detected to have been applied. The state of the tree
787 is restored to the original one
789 patches = [Patch(name, self.__patch_dir, self.__refs_dir)
795 if git.apply_diff(p.get_top(), p.get_bottom(), False):
796 merged.append(p.get_name())
803 def push_patch(self, name, empty = False):
804 """Pushes a patch on the stack
806 unapplied = self.get_unapplied()
807 assert(name in unapplied)
809 self.__begin_stack_check()
811 patch = Patch(name, self.__patch_dir, self.__refs_dir)
813 head = git.get_head()
814 bottom = patch.get_bottom()
815 top = patch.get_top()
820 # top != bottom always since we have a commit for each patch
822 # just make an empty patch (top = bottom = HEAD). This
823 # option is useful to allow undoing already merged
824 # patches. The top is updated by refresh_patch since we
825 # need an empty commit
826 patch.set_bottom(head, backup = True)
827 patch.set_top(head, backup = True)
830 # reset the backup information
831 patch.set_bottom(bottom, backup = True)
832 patch.set_top(top, backup = True)
836 # new patch needs to be refreshed.
837 # The current patch is empty after merge.
838 patch.set_bottom(head, backup = True)
839 patch.set_top(head, backup = True)
841 # Try the fast applying first. If this fails, fall back to the
843 if not git.apply_diff(bottom, top):
844 # if git.apply_diff() fails, the patch requires a diff3
845 # merge and can be reported as modified
848 # merge can fail but the patch needs to be pushed
850 git.merge(bottom, head, top)
851 except git.GitException, ex:
852 print >> sys.stderr, \
853 'The merge failed during "push". ' \
854 'Use "refresh" after fixing the conflicts'
856 append_string(self.__applied_file, name)
858 unapplied.remove(name)
859 f = file(self.__unapplied_file, 'w+')
860 f.writelines([line + '\n' for line in unapplied])
863 self.__set_current(name)
865 # head == bottom case doesn't need to refresh the patch
866 if empty or head != bottom:
868 # if the merge was OK and no conflicts, just refresh the patch
869 # The GIT cache was already updated by the merge operation
870 self.refresh_patch(cache_update = False)
872 raise StackException, str(ex)
877 name = self.get_current()
880 patch = Patch(name, self.__patch_dir, self.__refs_dir)
883 return patch.restore_old_boundaries()
885 def pop_patch(self, name):
886 """Pops the top patch from the stack
888 applied = self.get_applied()
890 assert(name in applied)
892 patch = Patch(name, self.__patch_dir, self.__refs_dir)
894 git.switch(patch.get_bottom())
896 # save the new applied list
897 idx = applied.index(name) + 1
899 popped = applied[:idx]
901 unapplied = popped + self.get_unapplied()
903 f = file(self.__unapplied_file, 'w+')
904 f.writelines([line + '\n' for line in unapplied])
910 f = file(self.__applied_file, 'w+')
911 f.writelines([line + '\n' for line in applied])
915 self.__set_current(None)
917 self.__set_current(applied[-1])
919 self.__end_stack_check()
921 def empty_patch(self, name):
922 """Returns True if the patch is empty
924 patch = Patch(name, self.__patch_dir, self.__refs_dir)
925 bottom = patch.get_bottom()
926 top = patch.get_top()
930 elif git.get_commit(top).get_tree() \
931 == git.get_commit(bottom).get_tree():
936 def rename_patch(self, oldname, newname):
937 applied = self.get_applied()
938 unapplied = self.get_unapplied()
940 if oldname == newname:
941 raise StackException, '"To" name and "from" name are the same'
943 if newname in applied or newname in unapplied:
944 raise StackException, 'Patch "%s" already exists' % newname
946 if oldname in unapplied:
947 Patch(oldname, self.__patch_dir, self.__refs_dir).rename(newname)
948 unapplied[unapplied.index(oldname)] = newname
950 f = file(self.__unapplied_file, 'w+')
951 f.writelines([line + '\n' for line in unapplied])
953 elif oldname in applied:
954 Patch(oldname, self.__patch_dir, self.__refs_dir).rename(newname)
955 if oldname == self.get_current():
956 self.__set_current(newname)
958 applied[applied.index(oldname)] = newname
960 f = file(self.__applied_file, 'w+')
961 f.writelines([line + '\n' for line in applied])
964 raise StackException, 'Unknown patch "%s"' % oldname