1 # -*- coding: utf-8 -*-
4 Copyright (C) 2006, Karl Hasselström <kha@treskal.com>
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License version 2 as
8 published by the Free Software Foundation.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 from optparse import OptionParser, make_option
23 from stgit.commands.common import *
24 from stgit.utils import *
25 from stgit.out import *
26 from stgit.run import *
27 from stgit import stack, git
29 help = 'StGit-ify any git commits made on top of your StGit stack'
30 usage = """%prog [options]
32 "assimilate" will repair three kinds of inconsistencies in your StGit
33 stack, all of them caused by using plain git commands on the branch:
35 1. If you have made regular git commits on top of your stack of
36 StGit patches, "assimilate" converts them to StGit patches,
37 preserving their contents.
39 2. Merge commits cannot become patches; if you have committed a
40 merge on top of your stack, "assimilate" will simply mark all
41 patches below the merge unapplied, since they are no longer
42 reachable. If this is not what you want, use "git reset" to get
43 rid of the merge and run "assimilate" again.
45 3. The applied patches are supposed to be precisely those that are
46 reachable from the branch head. If you have used e.g. "git reset"
47 to move the head, some applied patches may no longer be
48 reachable, and some unapplied patches may have become reachable.
49 "assimilate" will correct the appliedness of such patches.
51 Note that these are "inconsistencies", not "errors"; furthermore,
52 "assimilate" will repair them reliably. As long as you are satisfied
53 with the way "assimilate" handles them, you have no reason to avoid
54 causing them in the first place if that is convenient for you."""
56 directory = DirectoryHasRepository()
60 def __init__(self, id):
66 def __get_commit(self):
68 self.__commit = git.get_commit(self.id)
70 commit = property(__get_commit)
73 return '%s (%s)' % (self.id, self.patch)
77 return '<%s>' % str(self)
79 def read_commit_dag(branch):
80 out.start('Reading commit DAG')
83 for line in Run('git-rev-list', '--parents', '--all').output_lines():
87 commits[id] = Commit(id)
89 commits[cs[0]].parents.add(commits[id])
90 commits[id].children.add(commits[cs[0]])
91 for line in Run('git-show-ref').output_lines():
92 id, ref = line.split()
93 m = re.match(r'^refs/patches/%s/(.+)$' % branch, ref)
94 if m and not m.group(1).endswith('.log'):
99 return commits, patches
101 def func(parser, options, args):
102 """Assimilate a number of patches.
106 out.info('No commits to assimilate')
108 orig_applied = crt_series.get_applied()
109 orig_unapplied = crt_series.get_unapplied()
111 # If head == top, we're done.
112 head = git.get_commit(git.get_head()).get_id_hash()
113 top = crt_series.get_current_patch()
114 if top and head == top.get_top():
115 return nothing_to_do()
117 if crt_series.get_protected():
119 'This branch is protected. Modification is not permitted.')
121 # Find commits to assimilate, and applied patches.
122 commits, patches = read_commit_dag(crt_series.get_name())
126 while len(c.parents) == 1:
136 # Find patches hidden behind a merge.
144 todo |= c.parents - seen
148 out.warn(('%d patch%s are hidden below the merge commit'
149 % (len(hidden), ['es', ''][len(hidden) == 1])),
150 '%s,' % merge.id, 'and will be considered unapplied.')
152 # Assimilate any linear sequence of commits on top of a patch.
153 names = set(p.patch for p in patches)
154 def name_taken(name):
156 if applied and patchify:
157 out.start('Creating %d new patch%s'
158 % (len(patchify), ['es', ''][len(patchify) == 1]))
160 name = make_patch_name(p.commit.get_log(), name_taken)
161 out.info('Creating patch %s from commit %s' % (name, p.id))
162 aname, amail, adate = name_email_date(p.commit.get_author())
163 cname, cmail, cdate = name_email_date(p.commit.get_committer())
165 crt_series.new_patch(
166 name, can_edit = False, commit = False,
167 top = p.id, bottom = parent.id, message = p.commit.get_log(),
168 author_name = aname, author_email = amail, author_date = adate,
169 committer_name = cname, committer_email = cmail)
175 # Write the applied/unapplied files.
176 out.start('Checking patch appliedness')
177 unapplied = patches - set(applied)
178 applied_name_set = set(p.patch for p in applied)
179 unapplied_name_set = set(p.patch for p in unapplied)
180 patches_name_set = set(p.patch for p in patches)
181 orig_patches = orig_applied + orig_unapplied
182 orig_applied_name_set = set(orig_applied)
183 orig_unapplied_name_set = set(orig_unapplied)
184 orig_patches_name_set = set(orig_patches)
185 for name in orig_patches_name_set - patches_name_set:
186 out.info('%s is gone' % name)
187 for name in applied_name_set - orig_applied_name_set:
188 out.info('%s is now applied' % name)
189 for name in unapplied_name_set - orig_unapplied_name_set:
190 out.info('%s is now unapplied' % name)
191 orig_order = dict(zip(orig_patches, xrange(len(orig_patches))))
192 def patchname_cmp(p1, p2):
193 i1 = orig_order.get(p1, len(orig_order))
194 i2 = orig_order.get(p2, len(orig_order))
195 return cmp((i1, p1), (i2, p2))
196 crt_series.set_applied(p.patch for p in applied)
197 crt_series.set_unapplied(sorted(unapplied_name_set, cmp = patchname_cmp))