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.out import *
25 from stgit import git, basedir, templates
26 from stgit.config import config
27 from shutil import copyfile
30 # stack exception class
31 class StackException(Exception):
36 self.should_print = True
37 def __call__(self, x, until_test, prefix):
39 self.should_print = False
41 return x[0:len(prefix)] != prefix
47 __comment_prefix = 'STG:'
48 __patch_prefix = 'STG_PATCH:'
50 def __clean_comments(f):
51 """Removes lines marked for status in a commit file
55 # remove status-prefixed lines
58 patch_filter = FilterUntil()
59 until_test = lambda t: t == (__patch_prefix + '\n')
60 lines = [l for l in lines if patch_filter(l, until_test, __comment_prefix)]
62 # remove empty lines at the end
63 while len(lines) != 0 and lines[-1] == '\n':
66 f.seek(0); f.truncate()
69 def edit_file(series, line, comment, show_patch = True):
70 fname = '.stgitmsg.txt'
71 tmpl = templates.get_template('patchdescr.tmpl')
80 print >> f, __comment_prefix, comment
81 print >> f, __comment_prefix, \
82 'Lines prefixed with "%s" will be automatically removed.' \
84 print >> f, __comment_prefix, \
85 'Trailing empty lines will be automatically removed.'
88 print >> f, __patch_prefix
89 # series.get_patch(series.get_current()).get_top()
90 git.diff([], series.get_patch(series.get_current()).get_bottom(), None, f)
92 #Vim modeline must be near the end.
93 print >> f, __comment_prefix, 'vi: set textwidth=75 filetype=diff nobackup:'
114 """An object with stgit-like properties stored as files in a directory
116 def _set_dir(self, dir):
121 def create_empty_field(self, name):
122 create_empty_file(os.path.join(self.__dir, name))
124 def _get_field(self, name, multiline = False):
125 id_file = os.path.join(self.__dir, name)
126 if os.path.isfile(id_file):
127 line = read_string(id_file, multiline)
135 def _set_field(self, name, value, multiline = False):
136 fname = os.path.join(self.__dir, name)
137 if value and value != '':
138 write_string(fname, value, multiline)
139 elif os.path.isfile(fname):
143 class Patch(StgitObject):
144 """Basic patch implementation
146 def __init_refs(self):
147 self.__top_ref = self.__refs_base + '/' + self.__name
148 self.__log_ref = self.__top_ref + '.log'
150 def __init__(self, name, series_dir, refs_base):
151 self.__series_dir = series_dir
153 self._set_dir(os.path.join(self.__series_dir, self.__name))
154 self.__refs_base = refs_base
158 os.mkdir(self._dir())
159 self.create_empty_field('bottom')
160 self.create_empty_field('top')
163 for f in os.listdir(self._dir()):
164 os.remove(os.path.join(self._dir(), f))
165 os.rmdir(self._dir())
166 git.delete_ref(self.__top_ref)
167 if git.ref_exists(self.__log_ref):
168 git.delete_ref(self.__log_ref)
173 def rename(self, newname):
175 old_top_ref = self.__top_ref
176 old_log_ref = self.__log_ref
177 self.__name = newname
178 self._set_dir(os.path.join(self.__series_dir, self.__name))
181 git.rename_ref(old_top_ref, self.__top_ref)
182 if git.ref_exists(old_log_ref):
183 git.rename_ref(old_log_ref, self.__log_ref)
184 os.rename(olddir, self._dir())
186 def __update_top_ref(self, ref):
187 git.set_ref(self.__top_ref, ref)
189 def __update_log_ref(self, ref):
190 git.set_ref(self.__log_ref, ref)
192 def update_top_ref(self):
195 self.__update_top_ref(top)
197 def get_old_bottom(self):
198 return self._get_field('bottom.old')
200 def get_bottom(self):
201 return self._get_field('bottom')
203 def set_bottom(self, value, backup = False):
205 curr = self._get_field('bottom')
206 self._set_field('bottom.old', curr)
207 self._set_field('bottom', value)
209 def get_old_top(self):
210 return self._get_field('top.old')
213 return self._get_field('top')
215 def set_top(self, value, backup = False):
217 curr = self._get_field('top')
218 self._set_field('top.old', curr)
219 self._set_field('top', value)
220 self.__update_top_ref(value)
222 def restore_old_boundaries(self):
223 bottom = self._get_field('bottom.old')
224 top = self._get_field('top.old')
227 self._set_field('bottom', bottom)
228 self._set_field('top', top)
229 self.__update_top_ref(top)
234 def get_description(self):
235 return self._get_field('description', True)
237 def set_description(self, line):
238 self._set_field('description', line, True)
240 def get_authname(self):
241 return self._get_field('authname')
243 def set_authname(self, name):
244 self._set_field('authname', name or git.author().name)
246 def get_authemail(self):
247 return self._get_field('authemail')
249 def set_authemail(self, email):
250 self._set_field('authemail', email or git.author().email)
252 def get_authdate(self):
253 return self._get_field('authdate')
255 def set_authdate(self, date):
256 self._set_field('authdate', date or git.author().date)
258 def get_commname(self):
259 return self._get_field('commname')
261 def set_commname(self, name):
262 self._set_field('commname', name or git.committer().name)
264 def get_commemail(self):
265 return self._get_field('commemail')
267 def set_commemail(self, email):
268 self._set_field('commemail', email or git.committer().email)
271 return self._get_field('log')
273 def set_log(self, value, backup = False):
274 self._set_field('log', value)
275 self.__update_log_ref(value)
277 # The current StGIT metadata format version.
280 class PatchSet(StgitObject):
281 def __init__(self, name = None):
286 self.set_name (git.get_head_file())
287 self.__base_dir = basedir.get()
288 except git.GitException, ex:
289 raise StackException, 'GIT tree not initialised: %s' % ex
291 self._set_dir(os.path.join(self.__base_dir, 'patches', self.get_name()))
295 def set_name(self, name):
299 return self.__base_dir
302 """Return the head of the branch
304 crt = self.get_current_patch()
308 return self.get_base()
310 def get_protected(self):
311 return os.path.isfile(os.path.join(self._dir(), 'protected'))
314 protect_file = os.path.join(self._dir(), 'protected')
315 if not os.path.isfile(protect_file):
316 create_empty_file(protect_file)
319 protect_file = os.path.join(self._dir(), 'protected')
320 if os.path.isfile(protect_file):
321 os.remove(protect_file)
323 def __branch_descr(self):
324 return 'branch.%s.description' % self.get_name()
326 def get_description(self):
327 return config.get(self.__branch_descr()) or ''
329 def set_description(self, line):
331 config.set(self.__branch_descr(), line)
333 config.unset(self.__branch_descr())
335 def head_top_equal(self):
336 """Return true if the head and the top are the same
338 crt = self.get_current_patch()
340 # we don't care, no patches applied
342 return git.get_head() == crt.get_top()
344 def is_initialised(self):
345 """Checks if series is already initialised
347 return bool(config.get(self.format_version_key()))
350 class Series(PatchSet):
351 """Class including the operations on series
353 def __init__(self, name = None):
354 """Takes a series name as the parameter.
356 PatchSet.__init__(self, name)
358 # Update the branch to the latest format version if it is
359 # initialized, but don't touch it if it isn't.
360 self.update_to_current_format_version()
362 self.__refs_base = 'refs/patches/%s' % self.get_name()
364 self.__applied_file = os.path.join(self._dir(), 'applied')
365 self.__unapplied_file = os.path.join(self._dir(), 'unapplied')
366 self.__hidden_file = os.path.join(self._dir(), 'hidden')
368 # where this series keeps its patches
369 self.__patch_dir = os.path.join(self._dir(), 'patches')
372 self.__trash_dir = os.path.join(self._dir(), 'trash')
374 def format_version_key(self):
375 return 'branch.%s.stgit.stackformatversion' % self.get_name()
377 def update_to_current_format_version(self):
378 """Update a potentially older StGIT directory structure to the
379 latest version. Note: This function should depend as little as
380 possible on external functions that may change during a format
381 version bump, since it must remain able to process older formats."""
383 branch_dir = os.path.join(self._basedir(), 'patches', self.get_name())
384 def get_format_version():
385 """Return the integer format version number, or None if the
386 branch doesn't have any StGIT metadata at all, of any version."""
387 fv = config.get(self.format_version_key())
388 ofv = config.get('branch.%s.stgitformatversion' % self.get_name())
390 # Great, there's an explicitly recorded format version
391 # number, which means that the branch is initialized and
392 # of that exact version.
395 # Old name for the version info, upgrade it
396 config.set(self.format_version_key(), ofv)
397 config.unset('branch.%s.stgitformatversion' % self.get_name())
399 elif os.path.isdir(os.path.join(branch_dir, 'patches')):
400 # There's a .git/patches/<branch>/patches dirctory, which
401 # means this is an initialized version 1 branch.
403 elif os.path.isdir(branch_dir):
404 # There's a .git/patches/<branch> directory, which means
405 # this is an initialized version 0 branch.
408 # The branch doesn't seem to be initialized at all.
410 def set_format_version(v):
411 out.info('Upgraded branch %s to format version %d' % (self.get_name(), v))
412 config.set(self.format_version_key(), '%d' % v)
414 if not os.path.isdir(d):
417 if os.path.exists(f):
420 if git.ref_exists(ref):
424 if get_format_version() == 0:
425 mkdir(os.path.join(branch_dir, 'trash'))
426 patch_dir = os.path.join(branch_dir, 'patches')
428 refs_base = 'refs/patches/%s' % self.get_name()
429 for patch in (file(os.path.join(branch_dir, 'unapplied')).readlines()
430 + file(os.path.join(branch_dir, 'applied')).readlines()):
431 patch = patch.strip()
432 os.rename(os.path.join(branch_dir, patch),
433 os.path.join(patch_dir, patch))
434 Patch(patch, patch_dir, refs_base).update_top_ref()
435 set_format_version(1)
438 if get_format_version() == 1:
439 desc_file = os.path.join(branch_dir, 'description')
440 if os.path.isfile(desc_file):
441 desc = read_string(desc_file)
443 config.set('branch.%s.description' % self.get_name(), desc)
445 rm(os.path.join(branch_dir, 'current'))
446 rm_ref('refs/bases/%s' % self.get_name())
447 set_format_version(2)
449 # Make sure we're at the latest version.
450 if not get_format_version() in [None, FORMAT_VERSION]:
451 raise StackException('Branch %s is at format version %d, expected %d'
452 % (self.get_name(), get_format_version(), FORMAT_VERSION))
454 def __patch_name_valid(self, name):
455 """Raise an exception if the patch name is not valid.
457 if not name or re.search('[^\w.-]', name):
458 raise StackException, 'Invalid patch name: "%s"' % name
460 def get_patch(self, name):
461 """Return a Patch object for the given name
463 return Patch(name, self.__patch_dir, self.__refs_base)
465 def get_current_patch(self):
466 """Return a Patch object representing the topmost patch, or
467 None if there is no such patch."""
468 crt = self.get_current()
471 return self.get_patch(crt)
473 def get_current(self):
474 """Return the name of the topmost patch, or None if there is
477 applied = self.get_applied()
478 except StackException:
479 # No "applied" file: branch is not initialized.
484 # No patches applied.
487 def get_applied(self):
488 if not os.path.isfile(self.__applied_file):
489 raise StackException, 'Branch "%s" not initialised' % self.get_name()
490 return read_strings(self.__applied_file)
492 def get_unapplied(self):
493 if not os.path.isfile(self.__unapplied_file):
494 raise StackException, 'Branch "%s" not initialised' % self.get_name()
495 return read_strings(self.__unapplied_file)
497 def get_hidden(self):
498 if not os.path.isfile(self.__hidden_file):
500 return read_strings(self.__hidden_file)
503 # Return the parent of the bottommost patch, if there is one.
504 if os.path.isfile(self.__applied_file):
505 bottommost = file(self.__applied_file).readline().strip()
507 return self.get_patch(bottommost).get_bottom()
508 # No bottommost patch, so just return HEAD
509 return git.get_head()
511 def get_parent_remote(self):
512 value = config.get('branch.%s.remote' % self.get_name())
515 elif 'origin' in git.remotes_list():
516 out.note(('No parent remote declared for stack "%s",'
517 ' defaulting to "origin".' % self.get_name()),
518 ('Consider setting "branch.%s.remote" and'
519 ' "branch.%s.merge" with "git config".'
520 % (self.get_name(), self.get_name())))
523 raise StackException, 'Cannot find a parent remote for "%s"' % self.get_name()
525 def __set_parent_remote(self, remote):
526 value = config.set('branch.%s.remote' % self.get_name(), remote)
528 def get_parent_branch(self):
529 value = config.get('branch.%s.stgit.parentbranch' % self.get_name())
532 elif git.rev_parse('heads/origin'):
533 out.note(('No parent branch declared for stack "%s",'
534 ' defaulting to "heads/origin".' % self.get_name()),
535 ('Consider setting "branch.%s.stgit.parentbranch"'
536 ' with "git config".' % self.get_name()))
537 return 'heads/origin'
539 raise StackException, 'Cannot find a parent branch for "%s"' % self.get_name()
541 def __set_parent_branch(self, name):
542 if config.get('branch.%s.remote' % self.get_name()):
543 # Never set merge if remote is not set to avoid
544 # possibly-erroneous lookups into 'origin'
545 config.set('branch.%s.merge' % self.get_name(), name)
546 config.set('branch.%s.stgit.parentbranch' % self.get_name(), name)
548 def set_parent(self, remote, localbranch):
551 self.__set_parent_remote(remote)
552 self.__set_parent_branch(localbranch)
553 # We'll enforce this later
555 # raise StackException, 'Parent branch (%s) should be specified for %s' % localbranch, self.get_name()
557 def __patch_is_current(self, patch):
558 return patch.get_name() == self.get_current()
560 def patch_applied(self, name):
561 """Return true if the patch exists in the applied list
563 return name in self.get_applied()
565 def patch_unapplied(self, name):
566 """Return true if the patch exists in the unapplied list
568 return name in self.get_unapplied()
570 def patch_hidden(self, name):
571 """Return true if the patch is hidden.
573 return name in self.get_hidden()
575 def patch_exists(self, name):
576 """Return true if there is a patch with the given name, false
578 return self.patch_applied(name) or self.patch_unapplied(name) \
579 or self.patch_hidden(name)
581 def init(self, create_at=False, parent_remote=None, parent_branch=None):
582 """Initialises the stgit series
584 if self.is_initialised():
585 raise StackException, '%s already initialized' % self.get_name()
586 for d in [self._dir()]:
587 if os.path.exists(d):
588 raise StackException, '%s already exists' % d
590 if (create_at!=False):
591 git.create_branch(self.get_name(), create_at)
593 os.makedirs(self.__patch_dir)
595 self.set_parent(parent_remote, parent_branch)
597 self.create_empty_field('applied')
598 self.create_empty_field('unapplied')
599 self._set_field('orig-base', git.get_head())
601 config.set(self.format_version_key(), str(FORMAT_VERSION))
603 def rename(self, to_name):
606 to_stack = Series(to_name)
608 if to_stack.is_initialised():
609 raise StackException, '"%s" already exists' % to_stack.get_name()
611 patches = self.get_applied() + self.get_unapplied()
613 git.rename_branch(self.get_name(), to_name)
615 for patch in patches:
616 git.rename_ref('refs/patches/%s/%s' % (self.get_name(), patch),
617 'refs/patches/%s/%s' % (to_name, patch))
618 git.rename_ref('refs/patches/%s/%s.log' % (self.get_name(), patch),
619 'refs/patches/%s/%s.log' % (to_name, patch))
620 if os.path.isdir(self._dir()):
621 rename(os.path.join(self._basedir(), 'patches'),
622 self.get_name(), to_stack.get_name())
624 # Rename the config section
625 for k in ['branch.%s', 'branch.%s.stgit']:
626 config.rename_section(k % self.get_name(), k % to_name)
628 self.__init__(to_name)
630 def clone(self, target_series):
634 # allow cloning of branches not under StGIT control
635 base = self.get_base()
637 base = git.get_head()
638 Series(target_series).init(create_at = base)
639 new_series = Series(target_series)
641 # generate an artificial description file
642 new_series.set_description('clone of "%s"' % self.get_name())
644 # clone self's entire series as unapplied patches
646 # allow cloning of branches not under StGIT control
647 applied = self.get_applied()
648 unapplied = self.get_unapplied()
649 patches = applied + unapplied
652 patches = applied = unapplied = []
654 patch = self.get_patch(p)
655 newpatch = new_series.new_patch(p, message = patch.get_description(),
656 can_edit = False, unapplied = True,
657 bottom = patch.get_bottom(),
658 top = patch.get_top(),
659 author_name = patch.get_authname(),
660 author_email = patch.get_authemail(),
661 author_date = patch.get_authdate())
663 out.info('Setting log to %s' % patch.get_log())
664 newpatch.set_log(patch.get_log())
666 out.info('No log for %s' % p)
668 # fast forward the cloned series to self's top
669 new_series.forward_patches(applied)
671 # Clone parent informations
672 value = config.get('branch.%s.remote' % self.get_name())
674 config.set('branch.%s.remote' % target_series, value)
676 value = config.get('branch.%s.merge' % self.get_name())
678 config.set('branch.%s.merge' % target_series, value)
680 value = config.get('branch.%s.stgit.parentbranch' % self.get_name())
682 config.set('branch.%s.stgit.parentbranch' % target_series, value)
684 def delete(self, force = False):
685 """Deletes an stgit series
687 if self.is_initialised():
688 patches = self.get_unapplied() + self.get_applied()
689 if not force and patches:
690 raise StackException, \
691 'Cannot delete: the series still contains patches'
693 self.get_patch(p).delete()
695 # remove the trash directory if any
696 if os.path.exists(self.__trash_dir):
697 for fname in os.listdir(self.__trash_dir):
698 os.remove(os.path.join(self.__trash_dir, fname))
699 os.rmdir(self.__trash_dir)
701 # FIXME: find a way to get rid of those manual removals
702 # (move functionality to StgitObject ?)
703 if os.path.exists(self.__applied_file):
704 os.remove(self.__applied_file)
705 if os.path.exists(self.__unapplied_file):
706 os.remove(self.__unapplied_file)
707 if os.path.exists(self.__hidden_file):
708 os.remove(self.__hidden_file)
709 if os.path.exists(self._dir()+'/orig-base'):
710 os.remove(self._dir()+'/orig-base')
712 if not os.listdir(self.__patch_dir):
713 os.rmdir(self.__patch_dir)
715 out.warn('Patch directory %s is not empty' % self.__patch_dir)
718 os.removedirs(self._dir())
720 raise StackException('Series directory %s is not empty'
724 git.delete_branch(self.get_name())
726 out.warn('Could not delete branch "%s"' % self.get_name())
728 # Cleanup parent informations
729 # FIXME: should one day make use of git-config --section-remove,
730 # scheduled for 1.5.1
731 config.unset('branch.%s.remote' % self.get_name())
732 config.unset('branch.%s.merge' % self.get_name())
733 config.unset('branch.%s.stgit.parentbranch' % self.get_name())
734 config.unset(self.format_version_key())
736 def refresh_patch(self, files = None, message = None, edit = False,
739 author_name = None, author_email = None,
741 committer_name = None, committer_email = None,
742 backup = False, sign_str = None, log = 'refresh',
744 """Generates a new commit for the given patch
746 name = self.get_current()
748 raise StackException, 'No patches applied'
750 patch = self.get_patch(name)
752 descr = patch.get_description()
753 if not (message or descr):
759 if not message and edit:
760 descr = edit_file(self, descr.rstrip(), \
761 'Please edit the description for patch "%s" ' \
762 'above.' % name, show_patch)
765 author_name = patch.get_authname()
767 author_email = patch.get_authemail()
769 author_date = patch.get_authdate()
770 if not committer_name:
771 committer_name = patch.get_commname()
772 if not committer_email:
773 committer_email = patch.get_commemail()
776 descr = descr.rstrip()
777 if descr.find("\nSigned-off-by:") < 0 \
778 and descr.find("\nAcked-by:") < 0:
781 descr = '%s\n%s: %s <%s>\n' % (descr, sign_str,
782 committer_name, committer_email)
784 bottom = patch.get_bottom()
786 commit_id = git.commit(files = files,
787 message = descr, parents = [bottom],
788 cache_update = cache_update,
790 author_name = author_name,
791 author_email = author_email,
792 author_date = author_date,
793 committer_name = committer_name,
794 committer_email = committer_email)
796 patch.set_bottom(bottom, backup = backup)
797 patch.set_top(commit_id, backup = backup)
798 patch.set_description(descr)
799 patch.set_authname(author_name)
800 patch.set_authemail(author_email)
801 patch.set_authdate(author_date)
802 patch.set_commname(committer_name)
803 patch.set_commemail(committer_email)
806 self.log_patch(patch, log, notes)
810 def undo_refresh(self):
811 """Undo the patch boundaries changes caused by 'refresh'
813 name = self.get_current()
816 patch = self.get_patch(name)
817 old_bottom = patch.get_old_bottom()
818 old_top = patch.get_old_top()
820 # the bottom of the patch is not changed by refresh. If the
821 # old_bottom is different, there wasn't any previous 'refresh'
822 # command (probably only a 'push')
823 if old_bottom != patch.get_bottom() or old_top == patch.get_top():
824 raise StackException, 'No undo information available'
826 git.reset(tree_id = old_top, check_out = False)
827 if patch.restore_old_boundaries():
828 self.log_patch(patch, 'undo')
830 def new_patch(self, name, message = None, can_edit = True,
831 unapplied = False, show_patch = False,
832 top = None, bottom = None, commit = True,
833 author_name = None, author_email = None, author_date = None,
834 committer_name = None, committer_email = None,
835 before_existing = False):
836 """Creates a new patch
840 self.__patch_name_valid(name)
841 if self.patch_exists(name):
842 raise StackException, 'Patch "%s" already exists' % name
844 if not message and can_edit:
847 'Please enter the description for the patch above.',
852 head = git.get_head()
855 name = make_patch_name(descr, self.patch_exists)
857 patch = self.get_patch(name)
865 patch.set_bottom(bottom)
867 patch.set_description(descr)
868 patch.set_authname(author_name)
869 patch.set_authemail(author_email)
870 patch.set_authdate(author_date)
871 patch.set_commname(committer_name)
872 patch.set_commemail(committer_email)
875 insert_string(self.__applied_file, patch.get_name())
876 # no need to commit anything as the object is already
877 # present (mainly used by 'uncommit')
880 patches = [patch.get_name()] + self.get_unapplied()
881 write_strings(self.__unapplied_file, patches)
884 append_string(self.__applied_file, patch.get_name())
888 # create a commit for the patch (may be empty if top == bottom);
889 # only commit on top of the current branch
890 assert(unapplied or bottom == head)
891 top_commit = git.get_commit(top)
892 commit_id = git.commit(message = descr, parents = [bottom],
893 cache_update = False,
894 tree_id = top_commit.get_tree(),
895 allowempty = True, set_head = set_head,
896 author_name = author_name,
897 author_email = author_email,
898 author_date = author_date,
899 committer_name = committer_name,
900 committer_email = committer_email)
901 # set the patch top to the new commit
902 patch.set_top(commit_id)
904 self.log_patch(patch, 'new')
908 def delete_patch(self, name):
911 self.__patch_name_valid(name)
912 patch = self.get_patch(name)
914 if self.__patch_is_current(patch):
916 elif self.patch_applied(name):
917 raise StackException, 'Cannot remove an applied patch, "%s", ' \
918 'which is not current' % name
919 elif not name in self.get_unapplied():
920 raise StackException, 'Unknown patch "%s"' % name
922 # save the commit id to a trash file
923 write_string(os.path.join(self.__trash_dir, name), patch.get_top())
927 unapplied = self.get_unapplied()
928 unapplied.remove(name)
929 write_strings(self.__unapplied_file, unapplied)
931 def forward_patches(self, names):
932 """Try to fast-forward an array of patches.
934 On return, patches in names[0:returned_value] have been pushed on the
935 stack. Apply the rest with push_patch
937 unapplied = self.get_unapplied()
943 assert(name in unapplied)
945 patch = self.get_patch(name)
948 bottom = patch.get_bottom()
949 top = patch.get_top()
951 # top != bottom always since we have a commit for each patch
953 # reset the backup information. No logging since the
954 # patch hasn't changed
955 patch.set_bottom(head, backup = True)
956 patch.set_top(top, backup = True)
959 head_tree = git.get_commit(head).get_tree()
960 bottom_tree = git.get_commit(bottom).get_tree()
961 if head_tree == bottom_tree:
962 # We must just reparent this patch and create a new commit
964 descr = patch.get_description()
965 author_name = patch.get_authname()
966 author_email = patch.get_authemail()
967 author_date = patch.get_authdate()
968 committer_name = patch.get_commname()
969 committer_email = patch.get_commemail()
971 top_tree = git.get_commit(top).get_tree()
973 top = git.commit(message = descr, parents = [head],
974 cache_update = False,
977 author_name = author_name,
978 author_email = author_email,
979 author_date = author_date,
980 committer_name = committer_name,
981 committer_email = committer_email)
983 patch.set_bottom(head, backup = True)
984 patch.set_top(top, backup = True)
986 self.log_patch(patch, 'push(f)')
989 # stop the fast-forwarding, must do a real merge
993 unapplied.remove(name)
1000 append_strings(self.__applied_file, names[0:forwarded])
1001 write_strings(self.__unapplied_file, unapplied)
1005 def merged_patches(self, names):
1006 """Test which patches were merged upstream by reverse-applying
1007 them in reverse order. The function returns the list of
1008 patches detected to have been applied. The state of the tree
1009 is restored to the original one
1011 patches = [self.get_patch(name) for name in names]
1016 if git.apply_diff(p.get_top(), p.get_bottom()):
1017 merged.append(p.get_name())
1024 def push_patch(self, name, empty = False):
1025 """Pushes a patch on the stack
1027 unapplied = self.get_unapplied()
1028 assert(name in unapplied)
1030 patch = self.get_patch(name)
1032 head = git.get_head()
1033 bottom = patch.get_bottom()
1034 top = patch.get_top()
1039 # top != bottom always since we have a commit for each patch
1041 # just make an empty patch (top = bottom = HEAD). This
1042 # option is useful to allow undoing already merged
1043 # patches. The top is updated by refresh_patch since we
1044 # need an empty commit
1045 patch.set_bottom(head, backup = True)
1046 patch.set_top(head, backup = True)
1048 elif head == bottom:
1049 # reset the backup information. No need for logging
1050 patch.set_bottom(bottom, backup = True)
1051 patch.set_top(top, backup = True)
1055 # new patch needs to be refreshed.
1056 # The current patch is empty after merge.
1057 patch.set_bottom(head, backup = True)
1058 patch.set_top(head, backup = True)
1060 # Try the fast applying first. If this fails, fall back to the
1062 if not git.apply_diff(bottom, top):
1063 # if git.apply_diff() fails, the patch requires a diff3
1064 # merge and can be reported as modified
1067 # merge can fail but the patch needs to be pushed
1069 git.merge(bottom, head, top, recursive = True)
1070 except git.GitException, ex:
1071 out.error('The merge failed during "push".',
1072 'Use "refresh" after fixing the conflicts or'
1073 ' revert the operation with "push --undo".')
1075 append_string(self.__applied_file, name)
1077 unapplied.remove(name)
1078 write_strings(self.__unapplied_file, unapplied)
1080 # head == bottom case doesn't need to refresh the patch
1081 if empty or head != bottom:
1083 # if the merge was OK and no conflicts, just refresh the patch
1084 # The GIT cache was already updated by the merge operation
1089 self.refresh_patch(cache_update = False, log = log)
1091 # we store the correctly merged files only for
1092 # tracking the conflict history. Note that the
1093 # git.merge() operations should always leave the index
1094 # in a valid state (i.e. only stage 0 files)
1095 self.refresh_patch(cache_update = False, log = 'push(c)')
1096 raise StackException, str(ex)
1100 def undo_push(self):
1101 name = self.get_current()
1104 patch = self.get_patch(name)
1105 old_bottom = patch.get_old_bottom()
1106 old_top = patch.get_old_top()
1108 # the top of the patch is changed by a push operation only
1109 # together with the bottom (otherwise the top was probably
1110 # modified by 'refresh'). If they are both unchanged, there
1111 # was a fast forward
1112 if old_bottom == patch.get_bottom() and old_top != patch.get_top():
1113 raise StackException, 'No undo information available'
1116 self.pop_patch(name)
1117 ret = patch.restore_old_boundaries()
1119 self.log_patch(patch, 'undo')
1123 def pop_patch(self, name, keep = False):
1124 """Pops the top patch from the stack
1126 applied = self.get_applied()
1128 assert(name in applied)
1130 patch = self.get_patch(name)
1132 if git.get_head_file() == self.get_name():
1133 if keep and not git.apply_diff(git.get_head(), patch.get_bottom()):
1134 raise StackException(
1135 'Failed to pop patches while preserving the local changes')
1136 git.switch(patch.get_bottom(), keep)
1138 git.set_branch(self.get_name(), patch.get_bottom())
1140 # save the new applied list
1141 idx = applied.index(name) + 1
1143 popped = applied[:idx]
1145 unapplied = popped + self.get_unapplied()
1146 write_strings(self.__unapplied_file, unapplied)
1150 write_strings(self.__applied_file, applied)
1152 def empty_patch(self, name):
1153 """Returns True if the patch is empty
1155 self.__patch_name_valid(name)
1156 patch = self.get_patch(name)
1157 bottom = patch.get_bottom()
1158 top = patch.get_top()
1162 elif git.get_commit(top).get_tree() \
1163 == git.get_commit(bottom).get_tree():
1168 def rename_patch(self, oldname, newname):
1169 self.__patch_name_valid(newname)
1171 applied = self.get_applied()
1172 unapplied = self.get_unapplied()
1174 if oldname == newname:
1175 raise StackException, '"To" name and "from" name are the same'
1177 if newname in applied or newname in unapplied:
1178 raise StackException, 'Patch "%s" already exists' % newname
1180 if oldname in unapplied:
1181 self.get_patch(oldname).rename(newname)
1182 unapplied[unapplied.index(oldname)] = newname
1183 write_strings(self.__unapplied_file, unapplied)
1184 elif oldname in applied:
1185 self.get_patch(oldname).rename(newname)
1187 applied[applied.index(oldname)] = newname
1188 write_strings(self.__applied_file, applied)
1190 raise StackException, 'Unknown patch "%s"' % oldname
1192 def log_patch(self, patch, message, notes = None):
1193 """Generate a log commit for a patch
1195 top = git.get_commit(patch.get_top())
1196 old_log = patch.get_log()
1199 # replace the current log entry
1201 raise StackException, \
1202 'No log entry to annotate for patch "%s"' \
1205 log_commit = git.get_commit(old_log)
1206 msg = log_commit.get_log().split('\n')[0]
1207 log_parent = log_commit.get_parent()
1209 parents = [log_parent]
1213 # generate a new log entry
1215 msg = '%s\t%s' % (message, top.get_id_hash())
1222 msg += '\n\n' + notes
1224 log = git.commit(message = msg, parents = parents,
1225 cache_update = False, tree_id = top.get_tree(),
1229 def hide_patch(self, name):
1230 """Add the patch to the hidden list.
1232 unapplied = self.get_unapplied()
1233 if name not in unapplied:
1234 # keep the checking order for backward compatibility with
1235 # the old hidden patches functionality
1236 if self.patch_applied(name):
1237 raise StackException, 'Cannot hide applied patch "%s"' % name
1238 elif self.patch_hidden(name):
1239 raise StackException, 'Patch "%s" already hidden' % name
1241 raise StackException, 'Unknown patch "%s"' % name
1243 if not self.patch_hidden(name):
1244 # check needed for backward compatibility with the old
1245 # hidden patches functionality
1246 append_string(self.__hidden_file, name)
1248 unapplied.remove(name)
1249 write_strings(self.__unapplied_file, unapplied)
1251 def unhide_patch(self, name):
1252 """Remove the patch from the hidden list.
1254 hidden = self.get_hidden()
1255 if not name in hidden:
1256 if self.patch_applied(name) or self.patch_unapplied(name):
1257 raise StackException, 'Patch "%s" not hidden' % name
1259 raise StackException, 'Unknown patch "%s"' % name
1262 write_strings(self.__hidden_file, hidden)
1264 if not self.patch_applied(name) and not self.patch_unapplied(name):
1265 # check needed for backward compatibility with the old
1266 # hidden patches functionality
1267 append_string(self.__unapplied_file, name)