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