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 diff_str = git.diff(rev1 = series.get_patch(series.get_current()).get_bottom())
93 #Vim modeline must be near the end.
94 print >> f, __comment_prefix, 'vi: set textwidth=75 filetype=diff nobackup:'
115 """An object with stgit-like properties stored as files in a directory
117 def _set_dir(self, dir):
122 def create_empty_field(self, name):
123 create_empty_file(os.path.join(self.__dir, name))
125 def _get_field(self, name, multiline = False):
126 id_file = os.path.join(self.__dir, name)
127 if os.path.isfile(id_file):
128 line = read_string(id_file, multiline)
136 def _set_field(self, name, value, multiline = False):
137 fname = os.path.join(self.__dir, name)
138 if value and value != '':
139 write_string(fname, value, multiline)
140 elif os.path.isfile(fname):
144 class Patch(StgitObject):
145 """Basic patch implementation
147 def __init_refs(self):
148 self.__top_ref = self.__refs_base + '/' + self.__name
149 self.__log_ref = self.__top_ref + '.log'
151 def __init__(self, name, series_dir, refs_base):
152 self.__series_dir = series_dir
154 self._set_dir(os.path.join(self.__series_dir, self.__name))
155 self.__refs_base = refs_base
159 os.mkdir(self._dir())
160 self.create_empty_field('bottom')
161 self.create_empty_field('top')
164 for f in os.listdir(self._dir()):
165 os.remove(os.path.join(self._dir(), f))
166 os.rmdir(self._dir())
167 git.delete_ref(self.__top_ref)
168 if git.ref_exists(self.__log_ref):
169 git.delete_ref(self.__log_ref)
174 def rename(self, newname):
176 old_top_ref = self.__top_ref
177 old_log_ref = self.__log_ref
178 self.__name = newname
179 self._set_dir(os.path.join(self.__series_dir, self.__name))
182 git.rename_ref(old_top_ref, self.__top_ref)
183 if git.ref_exists(old_log_ref):
184 git.rename_ref(old_log_ref, self.__log_ref)
185 os.rename(olddir, self._dir())
187 def __update_top_ref(self, ref):
188 git.set_ref(self.__top_ref, ref)
190 def __update_log_ref(self, ref):
191 git.set_ref(self.__log_ref, ref)
193 def update_top_ref(self):
196 self.__update_top_ref(top)
198 def get_old_bottom(self):
199 return self._get_field('bottom.old')
201 def get_bottom(self):
202 return self._get_field('bottom')
204 def set_bottom(self, value, backup = False):
206 curr = self._get_field('bottom')
207 self._set_field('bottom.old', curr)
208 self._set_field('bottom', value)
210 def get_old_top(self):
211 return self._get_field('top.old')
214 return self._get_field('top')
216 def set_top(self, value, backup = False):
218 curr = self._get_field('top')
219 self._set_field('top.old', curr)
220 self._set_field('top', value)
221 self.__update_top_ref(value)
223 def restore_old_boundaries(self):
224 bottom = self._get_field('bottom.old')
225 top = self._get_field('top.old')
228 self._set_field('bottom', bottom)
229 self._set_field('top', top)
230 self.__update_top_ref(top)
235 def get_description(self):
236 return self._get_field('description', True)
238 def set_description(self, line):
239 self._set_field('description', line, True)
241 def get_authname(self):
242 return self._get_field('authname')
244 def set_authname(self, name):
245 self._set_field('authname', name or git.author().name)
247 def get_authemail(self):
248 return self._get_field('authemail')
250 def set_authemail(self, email):
251 self._set_field('authemail', email or git.author().email)
253 def get_authdate(self):
254 return self._get_field('authdate')
256 def set_authdate(self, date):
257 self._set_field('authdate', date or git.author().date)
259 def get_commname(self):
260 return self._get_field('commname')
262 def set_commname(self, name):
263 self._set_field('commname', name or git.committer().name)
265 def get_commemail(self):
266 return self._get_field('commemail')
268 def set_commemail(self, email):
269 self._set_field('commemail', email or git.committer().email)
272 return self._get_field('log')
274 def set_log(self, value, backup = False):
275 self._set_field('log', value)
276 self.__update_log_ref(value)
278 # The current StGIT metadata format version.
281 class PatchSet(StgitObject):
282 def __init__(self, name = None):
287 self.set_name (git.get_head_file())
288 self.__base_dir = basedir.get()
289 except git.GitException, ex:
290 raise StackException, 'GIT tree not initialised: %s' % ex
292 self._set_dir(os.path.join(self.__base_dir, 'patches', self.get_name()))
296 def set_name(self, name):
300 return self.__base_dir
303 """Return the head of the branch
305 crt = self.get_current_patch()
309 return self.get_base()
311 def get_protected(self):
312 return os.path.isfile(os.path.join(self._dir(), 'protected'))
315 protect_file = os.path.join(self._dir(), 'protected')
316 if not os.path.isfile(protect_file):
317 create_empty_file(protect_file)
320 protect_file = os.path.join(self._dir(), 'protected')
321 if os.path.isfile(protect_file):
322 os.remove(protect_file)
324 def __branch_descr(self):
325 return 'branch.%s.description' % self.get_name()
327 def get_description(self):
328 return config.get(self.__branch_descr()) or ''
330 def set_description(self, line):
332 config.set(self.__branch_descr(), line)
334 config.unset(self.__branch_descr())
336 def head_top_equal(self):
337 """Return true if the head and the top are the same
339 crt = self.get_current_patch()
341 # we don't care, no patches applied
343 return git.get_head() == crt.get_top()
345 def is_initialised(self):
346 """Checks if series is already initialised
348 return bool(config.get(self.format_version_key()))
351 class Series(PatchSet):
352 """Class including the operations on series
354 def __init__(self, name = None):
355 """Takes a series name as the parameter.
357 PatchSet.__init__(self, name)
359 # Update the branch to the latest format version if it is
360 # initialized, but don't touch it if it isn't.
361 self.update_to_current_format_version()
363 self.__refs_base = 'refs/patches/%s' % self.get_name()
365 self.__applied_file = os.path.join(self._dir(), 'applied')
366 self.__unapplied_file = os.path.join(self._dir(), 'unapplied')
367 self.__hidden_file = os.path.join(self._dir(), 'hidden')
369 # where this series keeps its patches
370 self.__patch_dir = os.path.join(self._dir(), 'patches')
373 self.__trash_dir = os.path.join(self._dir(), 'trash')
375 def format_version_key(self):
376 return 'branch.%s.stgit.stackformatversion' % self.get_name()
378 def update_to_current_format_version(self):
379 """Update a potentially older StGIT directory structure to the
380 latest version. Note: This function should depend as little as
381 possible on external functions that may change during a format
382 version bump, since it must remain able to process older formats."""
384 branch_dir = os.path.join(self._basedir(), 'patches', self.get_name())
385 def get_format_version():
386 """Return the integer format version number, or None if the
387 branch doesn't have any StGIT metadata at all, of any version."""
388 fv = config.get(self.format_version_key())
389 ofv = config.get('branch.%s.stgitformatversion' % self.get_name())
391 # Great, there's an explicitly recorded format version
392 # number, which means that the branch is initialized and
393 # of that exact version.
396 # Old name for the version info, upgrade it
397 config.set(self.format_version_key(), ofv)
398 config.unset('branch.%s.stgitformatversion' % self.get_name())
400 elif os.path.isdir(os.path.join(branch_dir, 'patches')):
401 # There's a .git/patches/<branch>/patches dirctory, which
402 # means this is an initialized version 1 branch.
404 elif os.path.isdir(branch_dir):
405 # There's a .git/patches/<branch> directory, which means
406 # this is an initialized version 0 branch.
409 # The branch doesn't seem to be initialized at all.
411 def set_format_version(v):
412 out.info('Upgraded branch %s to format version %d' % (self.get_name(), v))
413 config.set(self.format_version_key(), '%d' % v)
415 if not os.path.isdir(d):
418 if os.path.exists(f):
421 if git.ref_exists(ref):
425 if get_format_version() == 0:
426 mkdir(os.path.join(branch_dir, 'trash'))
427 patch_dir = os.path.join(branch_dir, 'patches')
429 refs_base = 'refs/patches/%s' % self.get_name()
430 for patch in (file(os.path.join(branch_dir, 'unapplied')).readlines()
431 + file(os.path.join(branch_dir, 'applied')).readlines()):
432 patch = patch.strip()
433 os.rename(os.path.join(branch_dir, patch),
434 os.path.join(patch_dir, patch))
435 Patch(patch, patch_dir, refs_base).update_top_ref()
436 set_format_version(1)
439 if get_format_version() == 1:
440 desc_file = os.path.join(branch_dir, 'description')
441 if os.path.isfile(desc_file):
442 desc = read_string(desc_file)
444 config.set('branch.%s.description' % self.get_name(), desc)
446 rm(os.path.join(branch_dir, 'current'))
447 rm_ref('refs/bases/%s' % self.get_name())
448 set_format_version(2)
450 # Make sure we're at the latest version.
451 if not get_format_version() in [None, FORMAT_VERSION]:
452 raise StackException('Branch %s is at format version %d, expected %d'
453 % (self.get_name(), get_format_version(), FORMAT_VERSION))
455 def __patch_name_valid(self, name):
456 """Raise an exception if the patch name is not valid.
458 if not name or re.search('[^\w.-]', name):
459 raise StackException, 'Invalid patch name: "%s"' % name
461 def get_patch(self, name):
462 """Return a Patch object for the given name
464 return Patch(name, self.__patch_dir, self.__refs_base)
466 def get_current_patch(self):
467 """Return a Patch object representing the topmost patch, or
468 None if there is no such patch."""
469 crt = self.get_current()
472 return self.get_patch(crt)
474 def get_current(self):
475 """Return the name of the topmost patch, or None if there is
478 applied = self.get_applied()
479 except StackException:
480 # No "applied" file: branch is not initialized.
485 # No patches applied.
488 def get_applied(self):
489 if not os.path.isfile(self.__applied_file):
490 raise StackException, 'Branch "%s" not initialised' % self.get_name()
491 return read_strings(self.__applied_file)
493 def get_unapplied(self):
494 if not os.path.isfile(self.__unapplied_file):
495 raise StackException, 'Branch "%s" not initialised' % self.get_name()
496 return read_strings(self.__unapplied_file)
498 def get_hidden(self):
499 if not os.path.isfile(self.__hidden_file):
501 return read_strings(self.__hidden_file)
504 # Return the parent of the bottommost patch, if there is one.
505 if os.path.isfile(self.__applied_file):
506 bottommost = file(self.__applied_file).readline().strip()
508 return self.get_patch(bottommost).get_bottom()
509 # No bottommost patch, so just return HEAD
510 return git.get_head()
512 def get_parent_remote(self):
513 value = config.get('branch.%s.remote' % self.get_name())
516 elif 'origin' in git.remotes_list():
517 out.note(('No parent remote declared for stack "%s",'
518 ' defaulting to "origin".' % self.get_name()),
519 ('Consider setting "branch.%s.remote" and'
520 ' "branch.%s.merge" with "git config".'
521 % (self.get_name(), self.get_name())))
524 raise StackException, 'Cannot find a parent remote for "%s"' % self.get_name()
526 def __set_parent_remote(self, remote):
527 value = config.set('branch.%s.remote' % self.get_name(), remote)
529 def get_parent_branch(self):
530 value = config.get('branch.%s.stgit.parentbranch' % self.get_name())
533 elif git.rev_parse('heads/origin'):
534 out.note(('No parent branch declared for stack "%s",'
535 ' defaulting to "heads/origin".' % self.get_name()),
536 ('Consider setting "branch.%s.stgit.parentbranch"'
537 ' with "git config".' % self.get_name()))
538 return 'heads/origin'
540 raise StackException, 'Cannot find a parent branch for "%s"' % self.get_name()
542 def __set_parent_branch(self, name):
543 if config.get('branch.%s.remote' % self.get_name()):
544 # Never set merge if remote is not set to avoid
545 # possibly-erroneous lookups into 'origin'
546 config.set('branch.%s.merge' % self.get_name(), name)
547 config.set('branch.%s.stgit.parentbranch' % self.get_name(), name)
549 def set_parent(self, remote, localbranch):
552 self.__set_parent_remote(remote)
553 self.__set_parent_branch(localbranch)
554 # We'll enforce this later
556 # raise StackException, 'Parent branch (%s) should be specified for %s' % localbranch, self.get_name()
558 def __patch_is_current(self, patch):
559 return patch.get_name() == self.get_current()
561 def patch_applied(self, name):
562 """Return true if the patch exists in the applied list
564 return name in self.get_applied()
566 def patch_unapplied(self, name):
567 """Return true if the patch exists in the unapplied list
569 return name in self.get_unapplied()
571 def patch_hidden(self, name):
572 """Return true if the patch is hidden.
574 return name in self.get_hidden()
576 def patch_exists(self, name):
577 """Return true if there is a patch with the given name, false
579 return self.patch_applied(name) or self.patch_unapplied(name) \
580 or self.patch_hidden(name)
582 def init(self, create_at=False, parent_remote=None, parent_branch=None):
583 """Initialises the stgit series
585 if self.is_initialised():
586 raise StackException, '%s already initialized' % self.get_name()
587 for d in [self._dir()]:
588 if os.path.exists(d):
589 raise StackException, '%s already exists' % d
591 if (create_at!=False):
592 git.create_branch(self.get_name(), create_at)
594 os.makedirs(self.__patch_dir)
596 self.set_parent(parent_remote, parent_branch)
598 self.create_empty_field('applied')
599 self.create_empty_field('unapplied')
600 self._set_field('orig-base', git.get_head())
602 config.set(self.format_version_key(), str(FORMAT_VERSION))
604 def rename(self, to_name):
607 to_stack = Series(to_name)
609 if to_stack.is_initialised():
610 raise StackException, '"%s" already exists' % to_stack.get_name()
612 patches = self.get_applied() + self.get_unapplied()
614 git.rename_branch(self.get_name(), to_name)
616 for patch in patches:
617 git.rename_ref('refs/patches/%s/%s' % (self.get_name(), patch),
618 'refs/patches/%s/%s' % (to_name, patch))
619 git.rename_ref('refs/patches/%s/%s.log' % (self.get_name(), patch),
620 'refs/patches/%s/%s.log' % (to_name, patch))
621 if os.path.isdir(self._dir()):
622 rename(os.path.join(self._basedir(), 'patches'),
623 self.get_name(), to_stack.get_name())
625 # Rename the config section
626 for k in ['branch.%s', 'branch.%s.stgit']:
627 config.rename_section(k % self.get_name(), k % to_name)
629 self.__init__(to_name)
631 def clone(self, target_series):
635 # allow cloning of branches not under StGIT control
636 base = self.get_base()
638 base = git.get_head()
639 Series(target_series).init(create_at = base)
640 new_series = Series(target_series)
642 # generate an artificial description file
643 new_series.set_description('clone of "%s"' % self.get_name())
645 # clone self's entire series as unapplied patches
647 # allow cloning of branches not under StGIT control
648 applied = self.get_applied()
649 unapplied = self.get_unapplied()
650 patches = applied + unapplied
653 patches = applied = unapplied = []
655 patch = self.get_patch(p)
656 newpatch = new_series.new_patch(p, message = patch.get_description(),
657 can_edit = False, unapplied = True,
658 bottom = patch.get_bottom(),
659 top = patch.get_top(),
660 author_name = patch.get_authname(),
661 author_email = patch.get_authemail(),
662 author_date = patch.get_authdate())
664 out.info('Setting log to %s' % patch.get_log())
665 newpatch.set_log(patch.get_log())
667 out.info('No log for %s' % p)
669 # fast forward the cloned series to self's top
670 new_series.forward_patches(applied)
672 # Clone parent informations
673 value = config.get('branch.%s.remote' % self.get_name())
675 config.set('branch.%s.remote' % target_series, value)
677 value = config.get('branch.%s.merge' % self.get_name())
679 config.set('branch.%s.merge' % target_series, value)
681 value = config.get('branch.%s.stgit.parentbranch' % self.get_name())
683 config.set('branch.%s.stgit.parentbranch' % target_series, value)
685 def delete(self, force = False):
686 """Deletes an stgit series
688 if self.is_initialised():
689 patches = self.get_unapplied() + self.get_applied()
690 if not force and patches:
691 raise StackException, \
692 'Cannot delete: the series still contains patches'
694 self.get_patch(p).delete()
696 # remove the trash directory if any
697 if os.path.exists(self.__trash_dir):
698 for fname in os.listdir(self.__trash_dir):
699 os.remove(os.path.join(self.__trash_dir, fname))
700 os.rmdir(self.__trash_dir)
702 # FIXME: find a way to get rid of those manual removals
703 # (move functionality to StgitObject ?)
704 if os.path.exists(self.__applied_file):
705 os.remove(self.__applied_file)
706 if os.path.exists(self.__unapplied_file):
707 os.remove(self.__unapplied_file)
708 if os.path.exists(self.__hidden_file):
709 os.remove(self.__hidden_file)
710 if os.path.exists(self._dir()+'/orig-base'):
711 os.remove(self._dir()+'/orig-base')
713 if not os.listdir(self.__patch_dir):
714 os.rmdir(self.__patch_dir)
716 out.warn('Patch directory %s is not empty' % self.__patch_dir)
719 os.removedirs(self._dir())
721 raise StackException('Series directory %s is not empty'
725 git.delete_branch(self.get_name())
727 out.warn('Could not delete branch "%s"' % self.get_name())
729 # Cleanup parent informations
730 # FIXME: should one day make use of git-config --section-remove,
731 # scheduled for 1.5.1
732 config.unset('branch.%s.remote' % self.get_name())
733 config.unset('branch.%s.merge' % self.get_name())
734 config.unset('branch.%s.stgit.parentbranch' % self.get_name())
735 config.unset(self.format_version_key())
737 def refresh_patch(self, files = None, message = None, edit = False,
740 author_name = None, author_email = None,
742 committer_name = None, committer_email = None,
743 backup = False, sign_str = None, log = 'refresh',
745 """Generates a new commit for the given patch
747 name = self.get_current()
749 raise StackException, 'No patches applied'
751 patch = self.get_patch(name)
753 descr = patch.get_description()
754 if not (message or descr):
760 if not message and edit:
761 descr = edit_file(self, descr.rstrip(), \
762 'Please edit the description for patch "%s" ' \
763 'above.' % name, show_patch)
766 author_name = patch.get_authname()
768 author_email = patch.get_authemail()
770 author_date = patch.get_authdate()
771 if not committer_name:
772 committer_name = patch.get_commname()
773 if not committer_email:
774 committer_email = patch.get_commemail()
777 descr = descr.rstrip()
778 if descr.find("\nSigned-off-by:") < 0 \
779 and descr.find("\nAcked-by:") < 0:
782 descr = '%s\n%s: %s <%s>\n' % (descr, sign_str,
783 committer_name, committer_email)
785 bottom = patch.get_bottom()
787 commit_id = git.commit(files = files,
788 message = descr, parents = [bottom],
789 cache_update = cache_update,
791 author_name = author_name,
792 author_email = author_email,
793 author_date = author_date,
794 committer_name = committer_name,
795 committer_email = committer_email)
797 patch.set_bottom(bottom, backup = backup)
798 patch.set_top(commit_id, backup = backup)
799 patch.set_description(descr)
800 patch.set_authname(author_name)
801 patch.set_authemail(author_email)
802 patch.set_authdate(author_date)
803 patch.set_commname(committer_name)
804 patch.set_commemail(committer_email)
807 self.log_patch(patch, log, notes)
811 def undo_refresh(self):
812 """Undo the patch boundaries changes caused by 'refresh'
814 name = self.get_current()
817 patch = self.get_patch(name)
818 old_bottom = patch.get_old_bottom()
819 old_top = patch.get_old_top()
821 # the bottom of the patch is not changed by refresh. If the
822 # old_bottom is different, there wasn't any previous 'refresh'
823 # command (probably only a 'push')
824 if old_bottom != patch.get_bottom() or old_top == patch.get_top():
825 raise StackException, 'No undo information available'
827 git.reset(tree_id = old_top, check_out = False)
828 if patch.restore_old_boundaries():
829 self.log_patch(patch, 'undo')
831 def new_patch(self, name, message = None, can_edit = True,
832 unapplied = False, show_patch = False,
833 top = None, bottom = None, commit = True,
834 author_name = None, author_email = None, author_date = None,
835 committer_name = None, committer_email = None,
836 before_existing = False):
837 """Creates a new patch
841 self.__patch_name_valid(name)
842 if self.patch_exists(name):
843 raise StackException, 'Patch "%s" already exists' % name
845 if not message and can_edit:
848 'Please enter the description for the patch above.',
853 head = git.get_head()
856 name = make_patch_name(descr, self.patch_exists)
858 patch = self.get_patch(name)
866 patch.set_bottom(bottom)
868 patch.set_description(descr)
869 patch.set_authname(author_name)
870 patch.set_authemail(author_email)
871 patch.set_authdate(author_date)
872 patch.set_commname(committer_name)
873 patch.set_commemail(committer_email)
876 insert_string(self.__applied_file, patch.get_name())
877 # no need to commit anything as the object is already
878 # present (mainly used by 'uncommit')
881 patches = [patch.get_name()] + self.get_unapplied()
882 write_strings(self.__unapplied_file, patches)
885 append_string(self.__applied_file, patch.get_name())
889 # create a commit for the patch (may be empty if top == bottom);
890 # only commit on top of the current branch
891 assert(unapplied or bottom == head)
892 top_commit = git.get_commit(top)
893 commit_id = git.commit(message = descr, parents = [bottom],
894 cache_update = False,
895 tree_id = top_commit.get_tree(),
896 allowempty = True, set_head = set_head,
897 author_name = author_name,
898 author_email = author_email,
899 author_date = author_date,
900 committer_name = committer_name,
901 committer_email = committer_email)
902 # set the patch top to the new commit
903 patch.set_top(commit_id)
905 self.log_patch(patch, 'new')
909 def delete_patch(self, name):
912 self.__patch_name_valid(name)
913 patch = self.get_patch(name)
915 if self.__patch_is_current(patch):
917 elif self.patch_applied(name):
918 raise StackException, 'Cannot remove an applied patch, "%s", ' \
919 'which is not current' % name
920 elif not name in self.get_unapplied():
921 raise StackException, 'Unknown patch "%s"' % name
923 # save the commit id to a trash file
924 write_string(os.path.join(self.__trash_dir, name), patch.get_top())
928 unapplied = self.get_unapplied()
929 unapplied.remove(name)
930 write_strings(self.__unapplied_file, unapplied)
932 def forward_patches(self, names):
933 """Try to fast-forward an array of patches.
935 On return, patches in names[0:returned_value] have been pushed on the
936 stack. Apply the rest with push_patch
938 unapplied = self.get_unapplied()
944 assert(name in unapplied)
946 patch = self.get_patch(name)
949 bottom = patch.get_bottom()
950 top = patch.get_top()
952 # top != bottom always since we have a commit for each patch
954 # reset the backup information. No logging since the
955 # patch hasn't changed
956 patch.set_bottom(head, backup = True)
957 patch.set_top(top, backup = True)
960 head_tree = git.get_commit(head).get_tree()
961 bottom_tree = git.get_commit(bottom).get_tree()
962 if head_tree == bottom_tree:
963 # We must just reparent this patch and create a new commit
965 descr = patch.get_description()
966 author_name = patch.get_authname()
967 author_email = patch.get_authemail()
968 author_date = patch.get_authdate()
969 committer_name = patch.get_commname()
970 committer_email = patch.get_commemail()
972 top_tree = git.get_commit(top).get_tree()
974 top = git.commit(message = descr, parents = [head],
975 cache_update = False,
978 author_name = author_name,
979 author_email = author_email,
980 author_date = author_date,
981 committer_name = committer_name,
982 committer_email = committer_email)
984 patch.set_bottom(head, backup = True)
985 patch.set_top(top, backup = True)
987 self.log_patch(patch, 'push(f)')
990 # stop the fast-forwarding, must do a real merge
994 unapplied.remove(name)
1001 append_strings(self.__applied_file, names[0:forwarded])
1002 write_strings(self.__unapplied_file, unapplied)
1006 def merged_patches(self, names):
1007 """Test which patches were merged upstream by reverse-applying
1008 them in reverse order. The function returns the list of
1009 patches detected to have been applied. The state of the tree
1010 is restored to the original one
1012 patches = [self.get_patch(name) for name in names]
1017 if git.apply_diff(p.get_top(), p.get_bottom()):
1018 merged.append(p.get_name())
1025 def push_patch(self, name, empty = False):
1026 """Pushes a patch on the stack
1028 unapplied = self.get_unapplied()
1029 assert(name in unapplied)
1031 patch = self.get_patch(name)
1033 head = git.get_head()
1034 bottom = patch.get_bottom()
1035 top = patch.get_top()
1040 # top != bottom always since we have a commit for each patch
1042 # just make an empty patch (top = bottom = HEAD). This
1043 # option is useful to allow undoing already merged
1044 # patches. The top is updated by refresh_patch since we
1045 # need an empty commit
1046 patch.set_bottom(head, backup = True)
1047 patch.set_top(head, backup = True)
1049 elif head == bottom:
1050 # reset the backup information. No need for logging
1051 patch.set_bottom(bottom, backup = True)
1052 patch.set_top(top, backup = True)
1056 # new patch needs to be refreshed.
1057 # The current patch is empty after merge.
1058 patch.set_bottom(head, backup = True)
1059 patch.set_top(head, backup = True)
1061 # Try the fast applying first. If this fails, fall back to the
1063 if not git.apply_diff(bottom, top):
1064 # if git.apply_diff() fails, the patch requires a diff3
1065 # merge and can be reported as modified
1068 # merge can fail but the patch needs to be pushed
1070 git.merge(bottom, head, top, recursive = True)
1071 except git.GitException, ex:
1072 out.error('The merge failed during "push".',
1073 'Use "refresh" after fixing the conflicts or'
1074 ' revert the operation with "push --undo".')
1076 append_string(self.__applied_file, name)
1078 unapplied.remove(name)
1079 write_strings(self.__unapplied_file, unapplied)
1081 # head == bottom case doesn't need to refresh the patch
1082 if empty or head != bottom:
1084 # if the merge was OK and no conflicts, just refresh the patch
1085 # The GIT cache was already updated by the merge operation
1090 self.refresh_patch(cache_update = False, log = log)
1092 # we store the correctly merged files only for
1093 # tracking the conflict history. Note that the
1094 # git.merge() operations should always leave the index
1095 # in a valid state (i.e. only stage 0 files)
1096 self.refresh_patch(cache_update = False, log = 'push(c)')
1097 raise StackException, str(ex)
1101 def undo_push(self):
1102 name = self.get_current()
1105 patch = self.get_patch(name)
1106 old_bottom = patch.get_old_bottom()
1107 old_top = patch.get_old_top()
1109 # the top of the patch is changed by a push operation only
1110 # together with the bottom (otherwise the top was probably
1111 # modified by 'refresh'). If they are both unchanged, there
1112 # was a fast forward
1113 if old_bottom == patch.get_bottom() and old_top != patch.get_top():
1114 raise StackException, 'No undo information available'
1117 self.pop_patch(name)
1118 ret = patch.restore_old_boundaries()
1120 self.log_patch(patch, 'undo')
1124 def pop_patch(self, name, keep = False):
1125 """Pops the top patch from the stack
1127 applied = self.get_applied()
1129 assert(name in applied)
1131 patch = self.get_patch(name)
1133 if git.get_head_file() == self.get_name():
1134 if keep and not git.apply_diff(git.get_head(), patch.get_bottom()):
1135 raise StackException(
1136 'Failed to pop patches while preserving the local changes')
1137 git.switch(patch.get_bottom(), keep)
1139 git.set_branch(self.get_name(), patch.get_bottom())
1141 # save the new applied list
1142 idx = applied.index(name) + 1
1144 popped = applied[:idx]
1146 unapplied = popped + self.get_unapplied()
1147 write_strings(self.__unapplied_file, unapplied)
1151 write_strings(self.__applied_file, applied)
1153 def empty_patch(self, name):
1154 """Returns True if the patch is empty
1156 self.__patch_name_valid(name)
1157 patch = self.get_patch(name)
1158 bottom = patch.get_bottom()
1159 top = patch.get_top()
1163 elif git.get_commit(top).get_tree() \
1164 == git.get_commit(bottom).get_tree():
1169 def rename_patch(self, oldname, newname):
1170 self.__patch_name_valid(newname)
1172 applied = self.get_applied()
1173 unapplied = self.get_unapplied()
1175 if oldname == newname:
1176 raise StackException, '"To" name and "from" name are the same'
1178 if newname in applied or newname in unapplied:
1179 raise StackException, 'Patch "%s" already exists' % newname
1181 if oldname in unapplied:
1182 self.get_patch(oldname).rename(newname)
1183 unapplied[unapplied.index(oldname)] = newname
1184 write_strings(self.__unapplied_file, unapplied)
1185 elif oldname in applied:
1186 self.get_patch(oldname).rename(newname)
1188 applied[applied.index(oldname)] = newname
1189 write_strings(self.__applied_file, applied)
1191 raise StackException, 'Unknown patch "%s"' % oldname
1193 def log_patch(self, patch, message, notes = None):
1194 """Generate a log commit for a patch
1196 top = git.get_commit(patch.get_top())
1197 old_log = patch.get_log()
1200 # replace the current log entry
1202 raise StackException, \
1203 'No log entry to annotate for patch "%s"' \
1206 log_commit = git.get_commit(old_log)
1207 msg = log_commit.get_log().split('\n')[0]
1208 log_parent = log_commit.get_parent()
1210 parents = [log_parent]
1214 # generate a new log entry
1216 msg = '%s\t%s' % (message, top.get_id_hash())
1223 msg += '\n\n' + notes
1225 log = git.commit(message = msg, parents = parents,
1226 cache_update = False, tree_id = top.get_tree(),
1230 def hide_patch(self, name):
1231 """Add the patch to the hidden list.
1233 unapplied = self.get_unapplied()
1234 if name not in unapplied:
1235 # keep the checking order for backward compatibility with
1236 # the old hidden patches functionality
1237 if self.patch_applied(name):
1238 raise StackException, 'Cannot hide applied patch "%s"' % name
1239 elif self.patch_hidden(name):
1240 raise StackException, 'Patch "%s" already hidden' % name
1242 raise StackException, 'Unknown patch "%s"' % name
1244 if not self.patch_hidden(name):
1245 # check needed for backward compatibility with the old
1246 # hidden patches functionality
1247 append_string(self.__hidden_file, name)
1249 unapplied.remove(name)
1250 write_strings(self.__unapplied_file, unapplied)
1252 def unhide_patch(self, name):
1253 """Remove the patch from the hidden list.
1255 hidden = self.get_hidden()
1256 if not name in hidden:
1257 if self.patch_applied(name) or self.patch_unapplied(name):
1258 raise StackException, 'Patch "%s" not hidden' % name
1260 raise StackException, 'Unknown patch "%s"' % name
1263 write_strings(self.__hidden_file, hidden)
1265 if not self.patch_applied(name) and not self.patch_unapplied(name):
1266 # check needed for backward compatibility with the old
1267 # hidden patches functionality
1268 append_string(self.__unapplied_file, name)