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.run import *
26 from stgit import git, basedir, templates
27 from stgit.config import config
28 from shutil import copyfile
31 # stack exception class
32 class StackException(Exception):
37 self.should_print = True
38 def __call__(self, x, until_test, prefix):
40 self.should_print = False
42 return x[0:len(prefix)] != prefix
48 __comment_prefix = 'STG:'
49 __patch_prefix = 'STG_PATCH:'
51 def __clean_comments(f):
52 """Removes lines marked for status in a commit file
56 # remove status-prefixed lines
59 patch_filter = FilterUntil()
60 until_test = lambda t: t == (__patch_prefix + '\n')
61 lines = [l for l in lines if patch_filter(l, until_test, __comment_prefix)]
63 # remove empty lines at the end
64 while len(lines) != 0 and lines[-1] == '\n':
67 f.seek(0); f.truncate()
70 def edit_file(series, line, comment, show_patch = True):
71 fname = '.stgitmsg.txt'
72 tmpl = templates.get_template('patchdescr.tmpl')
81 print >> f, __comment_prefix, comment
82 print >> f, __comment_prefix, \
83 'Lines prefixed with "%s" will be automatically removed.' \
85 print >> f, __comment_prefix, \
86 'Trailing empty lines will be automatically removed.'
89 print >> f, __patch_prefix
90 # series.get_patch(series.get_current()).get_top()
91 diff_str = git.diff(rev1 = series.get_patch(series.get_current()).get_bottom())
94 #Vim modeline must be near the end.
95 print >> f, __comment_prefix, 'vi: set textwidth=75 filetype=diff nobackup:'
100 f = file(fname, 'r+')
116 """An object with stgit-like properties stored as files in a directory
118 def _set_dir(self, dir):
123 def create_empty_field(self, name):
124 create_empty_file(os.path.join(self.__dir, name))
126 def _get_field(self, name, multiline = False):
127 id_file = os.path.join(self.__dir, name)
128 if os.path.isfile(id_file):
129 line = read_string(id_file, multiline)
137 def _set_field(self, name, value, multiline = False):
138 fname = os.path.join(self.__dir, name)
139 if value and value != '':
140 write_string(fname, value, multiline)
141 elif os.path.isfile(fname):
145 class Patch(StgitObject):
146 """Basic patch implementation
148 def __init_refs(self):
149 self.__top_ref = self.__refs_base + '/' + self.__name
150 self.__log_ref = self.__top_ref + '.log'
152 def __init__(self, name, series_dir, refs_base):
153 self.__series_dir = series_dir
155 self._set_dir(os.path.join(self.__series_dir, self.__name))
156 self.__refs_base = refs_base
160 os.mkdir(self._dir())
161 self.create_empty_field('bottom')
162 self.create_empty_field('top')
165 for f in os.listdir(self._dir()):
166 os.remove(os.path.join(self._dir(), f))
167 os.rmdir(self._dir())
168 git.delete_ref(self.__top_ref)
169 if git.ref_exists(self.__log_ref):
170 git.delete_ref(self.__log_ref)
175 def rename(self, newname):
177 old_top_ref = self.__top_ref
178 old_log_ref = self.__log_ref
179 self.__name = newname
180 self._set_dir(os.path.join(self.__series_dir, self.__name))
183 git.rename_ref(old_top_ref, self.__top_ref)
184 if git.ref_exists(old_log_ref):
185 git.rename_ref(old_log_ref, self.__log_ref)
186 os.rename(olddir, self._dir())
188 def __update_top_ref(self, ref):
189 git.set_ref(self.__top_ref, ref)
191 def __update_log_ref(self, ref):
192 git.set_ref(self.__log_ref, ref)
194 def update_top_ref(self):
197 self.__update_top_ref(top)
199 def get_old_bottom(self):
200 return self._get_field('bottom.old')
202 def get_bottom(self):
203 return self._get_field('bottom')
205 def set_bottom(self, value, backup = False):
207 curr = self._get_field('bottom')
208 self._set_field('bottom.old', curr)
209 self._set_field('bottom', value)
211 def get_old_top(self):
212 return self._get_field('top.old')
215 return self._get_field('top')
217 def set_top(self, value, backup = False):
219 curr = self._get_field('top')
220 self._set_field('top.old', curr)
221 self._set_field('top', value)
222 self.__update_top_ref(value)
224 def restore_old_boundaries(self):
225 bottom = self._get_field('bottom.old')
226 top = self._get_field('top.old')
229 self._set_field('bottom', bottom)
230 self._set_field('top', top)
231 self.__update_top_ref(top)
236 def get_description(self):
237 return self._get_field('description', True)
239 def set_description(self, line):
240 self._set_field('description', line, True)
242 def get_authname(self):
243 return self._get_field('authname')
245 def set_authname(self, name):
246 self._set_field('authname', name or git.author().name)
248 def get_authemail(self):
249 return self._get_field('authemail')
251 def set_authemail(self, email):
252 self._set_field('authemail', email or git.author().email)
254 def get_authdate(self):
255 return self._get_field('authdate')
257 def set_authdate(self, date):
258 self._set_field('authdate', date or git.author().date)
260 def get_commname(self):
261 return self._get_field('commname')
263 def set_commname(self, name):
264 self._set_field('commname', name or git.committer().name)
266 def get_commemail(self):
267 return self._get_field('commemail')
269 def set_commemail(self, email):
270 self._set_field('commemail', email or git.committer().email)
273 return self._get_field('log')
275 def set_log(self, value, backup = False):
276 self._set_field('log', value)
277 self.__update_log_ref(value)
279 # The current StGIT metadata format version.
282 class PatchSet(StgitObject):
283 def __init__(self, name = None):
288 self.set_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
293 self._set_dir(os.path.join(self.__base_dir, 'patches', self.get_name()))
297 def set_name(self, name):
301 return self.__base_dir
304 """Return the head of the branch
306 crt = self.get_current_patch()
310 return self.get_base()
312 def get_protected(self):
313 return os.path.isfile(os.path.join(self._dir(), 'protected'))
316 protect_file = os.path.join(self._dir(), 'protected')
317 if not os.path.isfile(protect_file):
318 create_empty_file(protect_file)
321 protect_file = os.path.join(self._dir(), 'protected')
322 if os.path.isfile(protect_file):
323 os.remove(protect_file)
325 def __branch_descr(self):
326 return 'branch.%s.description' % self.get_name()
328 def get_description(self):
329 return config.get(self.__branch_descr()) or ''
331 def set_description(self, line):
333 config.set(self.__branch_descr(), line)
335 config.unset(self.__branch_descr())
337 def head_top_equal(self):
338 """Return true if the head and the top are the same
340 crt = self.get_current_patch()
342 # we don't care, no patches applied
344 return git.get_head() == crt.get_top()
346 def is_initialised(self):
347 """Checks if series is already initialised
349 return bool(config.get(self.format_version_key()))
352 def shortlog(patches):
353 log = ''.join(Run('git-log', '--pretty=short',
354 p.get_top(), '^%s' % p.get_bottom()).raw_output()
356 return Run('git-shortlog').raw_input(log).raw_output()
358 class Series(PatchSet):
359 """Class including the operations on series
361 def __init__(self, name = None):
362 """Takes a series name as the parameter.
364 PatchSet.__init__(self, name)
366 # Update the branch to the latest format version if it is
367 # initialized, but don't touch it if it isn't.
368 self.update_to_current_format_version()
370 self.__refs_base = 'refs/patches/%s' % self.get_name()
372 self.__applied_file = os.path.join(self._dir(), 'applied')
373 self.__unapplied_file = os.path.join(self._dir(), 'unapplied')
374 self.__hidden_file = os.path.join(self._dir(), 'hidden')
376 # where this series keeps its patches
377 self.__patch_dir = os.path.join(self._dir(), 'patches')
380 self.__trash_dir = os.path.join(self._dir(), 'trash')
382 def format_version_key(self):
383 return 'branch.%s.stgit.stackformatversion' % self.get_name()
385 def update_to_current_format_version(self):
386 """Update a potentially older StGIT directory structure to the
387 latest version. Note: This function should depend as little as
388 possible on external functions that may change during a format
389 version bump, since it must remain able to process older formats."""
391 branch_dir = os.path.join(self._basedir(), 'patches', self.get_name())
392 def get_format_version():
393 """Return the integer format version number, or None if the
394 branch doesn't have any StGIT metadata at all, of any version."""
395 fv = config.get(self.format_version_key())
396 ofv = config.get('branch.%s.stgitformatversion' % self.get_name())
398 # Great, there's an explicitly recorded format version
399 # number, which means that the branch is initialized and
400 # of that exact version.
403 # Old name for the version info, upgrade it
404 config.set(self.format_version_key(), ofv)
405 config.unset('branch.%s.stgitformatversion' % self.get_name())
407 elif os.path.isdir(os.path.join(branch_dir, 'patches')):
408 # There's a .git/patches/<branch>/patches dirctory, which
409 # means this is an initialized version 1 branch.
411 elif os.path.isdir(branch_dir):
412 # There's a .git/patches/<branch> directory, which means
413 # this is an initialized version 0 branch.
416 # The branch doesn't seem to be initialized at all.
418 def set_format_version(v):
419 out.info('Upgraded branch %s to format version %d' % (self.get_name(), v))
420 config.set(self.format_version_key(), '%d' % v)
422 if not os.path.isdir(d):
425 if os.path.exists(f):
428 if git.ref_exists(ref):
432 if get_format_version() == 0:
433 mkdir(os.path.join(branch_dir, 'trash'))
434 patch_dir = os.path.join(branch_dir, 'patches')
436 refs_base = 'refs/patches/%s' % self.get_name()
437 for patch in (file(os.path.join(branch_dir, 'unapplied')).readlines()
438 + file(os.path.join(branch_dir, 'applied')).readlines()):
439 patch = patch.strip()
440 os.rename(os.path.join(branch_dir, patch),
441 os.path.join(patch_dir, patch))
442 Patch(patch, patch_dir, refs_base).update_top_ref()
443 set_format_version(1)
446 if get_format_version() == 1:
447 desc_file = os.path.join(branch_dir, 'description')
448 if os.path.isfile(desc_file):
449 desc = read_string(desc_file)
451 config.set('branch.%s.description' % self.get_name(), desc)
453 rm(os.path.join(branch_dir, 'current'))
454 rm_ref('refs/bases/%s' % self.get_name())
455 set_format_version(2)
457 # Make sure we're at the latest version.
458 if not get_format_version() in [None, FORMAT_VERSION]:
459 raise StackException('Branch %s is at format version %d, expected %d'
460 % (self.get_name(), get_format_version(), FORMAT_VERSION))
462 def __patch_name_valid(self, name):
463 """Raise an exception if the patch name is not valid.
465 if not name or re.search('[^\w.-]', name):
466 raise StackException, 'Invalid patch name: "%s"' % name
468 def get_patch(self, name):
469 """Return a Patch object for the given name
471 return Patch(name, self.__patch_dir, self.__refs_base)
473 def get_current_patch(self):
474 """Return a Patch object representing the topmost patch, or
475 None if there is no such patch."""
476 crt = self.get_current()
479 return self.get_patch(crt)
481 def get_current(self):
482 """Return the name of the topmost patch, or None if there is
485 applied = self.get_applied()
486 except StackException:
487 # No "applied" file: branch is not initialized.
492 # No patches applied.
495 def get_applied(self):
496 if not os.path.isfile(self.__applied_file):
497 raise StackException, 'Branch "%s" not initialised' % self.get_name()
498 return read_strings(self.__applied_file)
500 def get_unapplied(self):
501 if not os.path.isfile(self.__unapplied_file):
502 raise StackException, 'Branch "%s" not initialised' % self.get_name()
503 return read_strings(self.__unapplied_file)
505 def get_hidden(self):
506 if not os.path.isfile(self.__hidden_file):
508 return read_strings(self.__hidden_file)
511 # Return the parent of the bottommost patch, if there is one.
512 if os.path.isfile(self.__applied_file):
513 bottommost = file(self.__applied_file).readline().strip()
515 return self.get_patch(bottommost).get_bottom()
516 # No bottommost patch, so just return HEAD
517 return git.get_head()
519 def get_parent_remote(self):
520 value = config.get('branch.%s.remote' % self.get_name())
523 elif 'origin' in git.remotes_list():
524 out.note(('No parent remote declared for stack "%s",'
525 ' defaulting to "origin".' % self.get_name()),
526 ('Consider setting "branch.%s.remote" and'
527 ' "branch.%s.merge" with "git config".'
528 % (self.get_name(), self.get_name())))
531 raise StackException, 'Cannot find a parent remote for "%s"' % self.get_name()
533 def __set_parent_remote(self, remote):
534 value = config.set('branch.%s.remote' % self.get_name(), remote)
536 def get_parent_branch(self):
537 value = config.get('branch.%s.stgit.parentbranch' % self.get_name())
540 elif git.rev_parse('heads/origin'):
541 out.note(('No parent branch declared for stack "%s",'
542 ' defaulting to "heads/origin".' % self.get_name()),
543 ('Consider setting "branch.%s.stgit.parentbranch"'
544 ' with "git config".' % self.get_name()))
545 return 'heads/origin'
547 raise StackException, 'Cannot find a parent branch for "%s"' % self.get_name()
549 def __set_parent_branch(self, name):
550 if config.get('branch.%s.remote' % self.get_name()):
551 # Never set merge if remote is not set to avoid
552 # possibly-erroneous lookups into 'origin'
553 config.set('branch.%s.merge' % self.get_name(), name)
554 config.set('branch.%s.stgit.parentbranch' % self.get_name(), name)
556 def set_parent(self, remote, localbranch):
559 self.__set_parent_remote(remote)
560 self.__set_parent_branch(localbranch)
561 # We'll enforce this later
563 # raise StackException, 'Parent branch (%s) should be specified for %s' % localbranch, self.get_name()
565 def __patch_is_current(self, patch):
566 return patch.get_name() == self.get_current()
568 def patch_applied(self, name):
569 """Return true if the patch exists in the applied list
571 return name in self.get_applied()
573 def patch_unapplied(self, name):
574 """Return true if the patch exists in the unapplied list
576 return name in self.get_unapplied()
578 def patch_hidden(self, name):
579 """Return true if the patch is hidden.
581 return name in self.get_hidden()
583 def patch_exists(self, name):
584 """Return true if there is a patch with the given name, false
586 return self.patch_applied(name) or self.patch_unapplied(name) \
587 or self.patch_hidden(name)
589 def init(self, create_at=False, parent_remote=None, parent_branch=None):
590 """Initialises the stgit series
592 if self.is_initialised():
593 raise StackException, '%s already initialized' % self.get_name()
594 for d in [self._dir()]:
595 if os.path.exists(d):
596 raise StackException, '%s already exists' % d
598 if (create_at!=False):
599 git.create_branch(self.get_name(), create_at)
601 os.makedirs(self.__patch_dir)
603 self.set_parent(parent_remote, parent_branch)
605 self.create_empty_field('applied')
606 self.create_empty_field('unapplied')
607 self._set_field('orig-base', git.get_head())
609 config.set(self.format_version_key(), str(FORMAT_VERSION))
611 def rename(self, to_name):
614 to_stack = Series(to_name)
616 if to_stack.is_initialised():
617 raise StackException, '"%s" already exists' % to_stack.get_name()
619 patches = self.get_applied() + self.get_unapplied()
621 git.rename_branch(self.get_name(), to_name)
623 for patch in patches:
624 git.rename_ref('refs/patches/%s/%s' % (self.get_name(), patch),
625 'refs/patches/%s/%s' % (to_name, patch))
626 git.rename_ref('refs/patches/%s/%s.log' % (self.get_name(), patch),
627 'refs/patches/%s/%s.log' % (to_name, patch))
628 if os.path.isdir(self._dir()):
629 rename(os.path.join(self._basedir(), 'patches'),
630 self.get_name(), to_stack.get_name())
632 # Rename the config section
633 for k in ['branch.%s', 'branch.%s.stgit']:
634 config.rename_section(k % self.get_name(), k % to_name)
636 self.__init__(to_name)
638 def clone(self, target_series):
642 # allow cloning of branches not under StGIT control
643 base = self.get_base()
645 base = git.get_head()
646 Series(target_series).init(create_at = base)
647 new_series = Series(target_series)
649 # generate an artificial description file
650 new_series.set_description('clone of "%s"' % self.get_name())
652 # clone self's entire series as unapplied patches
654 # allow cloning of branches not under StGIT control
655 applied = self.get_applied()
656 unapplied = self.get_unapplied()
657 patches = applied + unapplied
660 patches = applied = unapplied = []
662 patch = self.get_patch(p)
663 newpatch = new_series.new_patch(p, message = patch.get_description(),
664 can_edit = False, unapplied = True,
665 bottom = patch.get_bottom(),
666 top = patch.get_top(),
667 author_name = patch.get_authname(),
668 author_email = patch.get_authemail(),
669 author_date = patch.get_authdate())
671 out.info('Setting log to %s' % patch.get_log())
672 newpatch.set_log(patch.get_log())
674 out.info('No log for %s' % p)
676 # fast forward the cloned series to self's top
677 new_series.forward_patches(applied)
679 # Clone parent informations
680 value = config.get('branch.%s.remote' % self.get_name())
682 config.set('branch.%s.remote' % target_series, value)
684 value = config.get('branch.%s.merge' % self.get_name())
686 config.set('branch.%s.merge' % target_series, value)
688 value = config.get('branch.%s.stgit.parentbranch' % self.get_name())
690 config.set('branch.%s.stgit.parentbranch' % target_series, value)
692 def delete(self, force = False):
693 """Deletes an stgit series
695 if self.is_initialised():
696 patches = self.get_unapplied() + self.get_applied()
697 if not force and patches:
698 raise StackException, \
699 'Cannot delete: the series still contains patches'
701 self.get_patch(p).delete()
703 # remove the trash directory if any
704 if os.path.exists(self.__trash_dir):
705 for fname in os.listdir(self.__trash_dir):
706 os.remove(os.path.join(self.__trash_dir, fname))
707 os.rmdir(self.__trash_dir)
709 # FIXME: find a way to get rid of those manual removals
710 # (move functionality to StgitObject ?)
711 if os.path.exists(self.__applied_file):
712 os.remove(self.__applied_file)
713 if os.path.exists(self.__unapplied_file):
714 os.remove(self.__unapplied_file)
715 if os.path.exists(self.__hidden_file):
716 os.remove(self.__hidden_file)
717 if os.path.exists(self._dir()+'/orig-base'):
718 os.remove(self._dir()+'/orig-base')
720 if not os.listdir(self.__patch_dir):
721 os.rmdir(self.__patch_dir)
723 out.warn('Patch directory %s is not empty' % self.__patch_dir)
726 os.removedirs(self._dir())
728 raise StackException('Series directory %s is not empty'
732 git.delete_branch(self.get_name())
734 out.warn('Could not delete branch "%s"' % self.get_name())
736 # Cleanup parent informations
737 # FIXME: should one day make use of git-config --section-remove,
738 # scheduled for 1.5.1
739 config.unset('branch.%s.remote' % self.get_name())
740 config.unset('branch.%s.merge' % self.get_name())
741 config.unset('branch.%s.stgit.parentbranch' % self.get_name())
742 config.unset(self.format_version_key())
744 def refresh_patch(self, files = None, message = None, edit = False,
747 author_name = None, author_email = None,
749 committer_name = None, committer_email = None,
750 backup = False, sign_str = None, log = 'refresh',
752 """Generates a new commit for the given patch
754 name = self.get_current()
756 raise StackException, 'No patches applied'
758 patch = self.get_patch(name)
760 descr = patch.get_description()
761 if not (message or descr):
767 if not message and edit:
768 descr = edit_file(self, descr.rstrip(), \
769 'Please edit the description for patch "%s" ' \
770 'above.' % name, show_patch)
773 author_name = patch.get_authname()
775 author_email = patch.get_authemail()
777 author_date = patch.get_authdate()
778 if not committer_name:
779 committer_name = patch.get_commname()
780 if not committer_email:
781 committer_email = patch.get_commemail()
783 descr = add_sign_line(descr, sign_str, 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)