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 stgit.argparse import opt
22 from stgit.commands.common import *
23 from stgit.utils import *
24 from stgit.out import *
25 from stgit.run import *
26 from stgit import stack, git
28 help = 'Fix StGit metadata if branch was modified with git commands'
31 If you modify an StGit stack (branch) with some git commands -- such
32 as commit, pull, merge, and rebase -- you will leave the StGit
33 metadata in an inconsistent state. In that situation, you have two
36 1. Use "git reset" or similar to undo the effect of the git
39 2. Use "stg repair". This will fix up the StGit metadata to
40 accomodate the modifications to the branch. Specifically, it will
43 * If you have made regular git commits on top of your stack of
44 StGit patches, "stg repair" makes new StGit patches out of
45 them, preserving their contents.
47 * However, merge commits cannot become patches; if you have
48 committed a merge on top of your stack, "repair" will simply
49 mark all patches below the merge unapplied, since they are no
50 longer reachable. If this is not what you want, use "git
51 reset" to get rid of the merge and run "stg repair" again.
53 * The applied patches are supposed to be precisely those that
54 are reachable from the branch head. If you have used e.g.
55 "git reset" to move the head, some applied patches may no
56 longer be reachable, and some unapplied patches may have
57 become reachable. "stg repair" will correct the appliedness
60 "stg repair" will fix these inconsistencies reliably, so as long
61 as you like what it does, you have no reason to avoid causing
62 them in the first place. For example, you might find it
63 convenient to make commits with a graphical tool and then have
64 "stg repair" make proper patches of the commits.
66 NOTE: If using git commands on the stack was a mistake, running "stg
67 repair" is _not_ what you want. In that case, what you want is option
72 directory = DirectoryGotoToplevel()
75 def __init__(self, id):
81 def __get_commit(self):
83 self.__commit = git.get_commit(self.id)
85 commit = property(__get_commit)
88 return '%s (%s)' % (self.id, self.patch)
92 return '<%s>' % str(self)
94 def read_commit_dag(branch):
95 out.start('Reading commit DAG')
98 for line in Run('git', 'rev-list', '--parents', '--all').output_lines():
101 if not id in commits:
102 commits[id] = Commit(id)
104 commits[cs[0]].parents.add(commits[id])
105 commits[id].children.add(commits[cs[0]])
106 for line in Run('git', 'show-ref').output_lines():
107 id, ref = line.split()
108 m = re.match(r'^refs/patches/%s/(.+)$' % branch, ref)
109 if m and not m.group(1).endswith('.log'):
114 return commits, patches
116 def func(parser, options, args):
117 """Repair inconsistencies in StGit metadata."""
119 orig_applied = crt_series.get_applied()
120 orig_unapplied = crt_series.get_unapplied()
122 if crt_series.get_protected():
124 'This branch is protected. Modification is not permitted.')
126 # Find commits that aren't patches, and applied patches.
127 head = git.get_commit(git.get_head()).get_id_hash()
128 commits, patches = read_commit_dag(crt_series.get_name())
130 patchify = [] # commits to definitely patchify
131 maybe_patchify = [] # commits to patchify if we find a patch below them
133 while len(c.parents) == 1:
137 patchify.extend(maybe_patchify)
140 maybe_patchify.append(c)
145 # Find patches hidden behind a merge.
153 todo |= c.parents - seen
157 out.warn(('%d patch%s are hidden below the merge commit'
158 % (len(hidden), ['es', ''][len(hidden) == 1])),
159 '%s,' % merge.id, 'and will be considered unapplied.')
161 # Make patches of any linear sequence of commits on top of a patch.
162 names = set(p.patch for p in patches)
163 def name_taken(name):
165 if applied and patchify:
166 out.start('Creating %d new patch%s'
167 % (len(patchify), ['es', ''][len(patchify) == 1]))
169 name = make_patch_name(p.commit.get_log(), name_taken)
170 out.info('Creating patch %s from commit %s' % (name, p.id))
171 aname, amail, adate = name_email_date(p.commit.get_author())
172 cname, cmail, cdate = name_email_date(p.commit.get_committer())
174 crt_series.new_patch(
175 name, can_edit = False, commit = False,
176 top = p.id, bottom = parent.id, message = p.commit.get_log(),
177 author_name = aname, author_email = amail, author_date = adate,
178 committer_name = cname, committer_email = cmail)
184 # Write the applied/unapplied files.
185 out.start('Checking patch appliedness')
186 unapplied = patches - set(applied)
187 applied_name_set = set(p.patch for p in applied)
188 unapplied_name_set = set(p.patch for p in unapplied)
189 patches_name_set = set(p.patch for p in patches)
190 orig_patches = orig_applied + orig_unapplied
191 orig_applied_name_set = set(orig_applied)
192 orig_unapplied_name_set = set(orig_unapplied)
193 orig_patches_name_set = set(orig_patches)
194 for name in orig_patches_name_set - patches_name_set:
195 out.info('%s is gone' % name)
196 for name in applied_name_set - orig_applied_name_set:
197 out.info('%s is now applied' % name)
198 for name in unapplied_name_set - orig_unapplied_name_set:
199 out.info('%s is now unapplied' % name)
200 orig_order = dict(zip(orig_patches, xrange(len(orig_patches))))
201 def patchname_cmp(p1, p2):
202 i1 = orig_order.get(p1, len(orig_order))
203 i2 = orig_order.get(p2, len(orig_order))
204 return cmp((i1, p1), (i2, p2))
205 crt_series.set_applied(p.patch for p in applied)
206 crt_series.set_unapplied(sorted(unapplied_name_set, cmp = patchname_cmp))