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 "stg undo" to undo the effect of the git commands. (If you
38 know what you are doing and want more control, "git reset" or
39 similar will work too.)
41 2. Use "stg repair". This will fix up the StGit metadata to
42 accommodate the modifications to the branch. Specifically, it will
45 * If you have made regular git commits on top of your stack of
46 StGit patches, "stg repair" makes new StGit patches out of
47 them, preserving their contents.
49 * However, merge commits cannot become patches; if you have
50 committed a merge on top of your stack, "repair" will simply
51 mark all patches below the merge unapplied, since they are no
52 longer reachable. If this is not what you want, use "stg
53 undo" to get rid of the merge and run "stg repair" again.
55 * The applied patches are supposed to be precisely those that
56 are reachable from the branch head. If you have used e.g.
57 "git reset" to move the head, some applied patches may no
58 longer be reachable, and some unapplied patches may have
59 become reachable. "stg repair" will correct the appliedness
62 "stg repair" will fix these inconsistencies reliably, so as long
63 as you like what it does, you have no reason to avoid causing
64 them in the first place. For example, you might find it
65 convenient to make commits with a graphical tool and then have
66 "stg repair" make proper patches of the commits.
68 NOTE: If using git commands on the stack was a mistake, running "stg
69 repair" is _not_ what you want. In that case, what you want is option
75 directory
= DirectoryGotoToplevel(log
= True)
78 def __init__(self
, id):
84 def __get_commit(self
):
86 self
.__commit
= git
.get_commit(self
.id)
88 commit
= property(__get_commit
)
91 return '%s (%s)' % (self
.id, self
.patch
)
95 return '<%s>' % str(self
)
97 def read_commit_dag(branch
):
98 out
.start('Reading commit DAG')
101 for line
in Run('git', 'rev-list', '--parents', '--all').output_lines():
104 if not id in commits
:
105 commits
[id] = Commit(id)
107 commits
[cs
[0]].parents
.add(commits
[id])
108 commits
[id].children
.add(commits
[cs
[0]])
109 for line
in Run('git', 'show-ref').output_lines():
110 id, ref
= line
.split()
111 m
= re
.match(r
'^refs/patches/%s/(.+)$' % re
.escape(branch
), ref
)
112 if m
and not m
.group(1).endswith('.log'):
117 return commits
, patches
119 def func(parser
, options
, args
):
120 """Repair inconsistencies in StGit metadata."""
122 orig_applied
= crt_series
.get_applied()
123 orig_unapplied
= crt_series
.get_unapplied()
124 orig_hidden
= crt_series
.get_hidden()
126 if crt_series
.get_protected():
128 'This branch is protected. Modification is not permitted.')
130 # Find commits that aren't patches, and applied patches.
131 head
= git
.get_commit(git
.get_head()).get_id_hash()
132 commits
, patches
= read_commit_dag(crt_series
.get_name())
134 patchify
= [] # commits to definitely patchify
135 maybe_patchify
= [] # commits to patchify if we find a patch below them
137 while len(c
.parents
) == 1:
141 patchify
.extend(maybe_patchify
)
144 maybe_patchify
.append(c
)
149 # Find patches hidden behind a merge.
157 todo |
= c
.parents
- seen
161 out
.warn(('%d patch%s are hidden below the merge commit'
162 % (len(hidden
), ['es', ''][len(hidden
) == 1])),
163 '%s,' % merge
.id, 'and will be considered unapplied.')
165 # Make patches of any linear sequence of commits on top of a patch.
166 names
= set(p
.patch
for p
in patches
)
167 def name_taken(name
):
169 if applied
and patchify
:
170 out
.start('Creating %d new patch%s'
171 % (len(patchify
), ['es', ''][len(patchify
) == 1]))
173 name
= make_patch_name(p
.commit
.get_log(), name_taken
)
174 out
.info('Creating patch %s from commit %s' % (name
, p
.id))
175 aname
, amail
, adate
= name_email_date(p
.commit
.get_author())
176 cname
, cmail
, cdate
= name_email_date(p
.commit
.get_committer())
178 crt_series
.new_patch(
179 name
, can_edit
= False, commit
= False,
180 top
= p
.id, bottom
= parent
.id, message
= p
.commit
.get_log(),
181 author_name
= aname
, author_email
= amail
, author_date
= adate
,
182 committer_name
= cname
, committer_email
= cmail
)
189 orig_patches
= orig_applied
+ orig_unapplied
+ orig_hidden
190 orig_applied_name_set
= set(orig_applied
)
191 orig_unapplied_name_set
= set(orig_unapplied
)
192 orig_hidden_name_set
= set(orig_hidden
)
193 orig_patches_name_set
= set(orig_patches
)
194 hidden
= [p
for p
in patches
if p
.patch
in orig_hidden_name_set
]
196 # Write the applied/unapplied files.
197 out
.start('Checking patch appliedness')
198 unapplied
= patches
- set(applied
) - set(hidden
)
199 applied_name_set
= set(p
.patch
for p
in applied
)
200 unapplied_name_set
= set(p
.patch
for p
in unapplied
)
201 hidden_name_set
= set(p
.patch
for p
in hidden
)
202 patches_name_set
= set(p
.patch
for p
in patches
)
203 for name
in orig_patches_name_set
- patches_name_set
:
204 out
.info('%s is gone' % name
)
205 for name
in applied_name_set
- orig_applied_name_set
:
206 out
.info('%s is now applied' % name
)
207 for name
in unapplied_name_set
- orig_unapplied_name_set
:
208 out
.info('%s is now unapplied' % name
)
209 for name
in hidden_name_set
- orig_hidden_name_set
:
210 out
.info('%s is now hidden' % name
)
211 orig_order
= dict(zip(orig_patches
, xrange(len(orig_patches
))))
212 def patchname_cmp(p1
, p2
):
213 i1
= orig_order
.get(p1
, len(orig_order
))
214 i2
= orig_order
.get(p2
, len(orig_order
))
215 return cmp((i1
, p1
), (i2
, p2
))
216 crt_series
.set_applied(p
.patch
for p
in applied
)
217 crt_series
.set_unapplied(sorted(unapplied_name_set
, cmp = patchname_cmp
))
218 crt_series
.set_hidden(sorted(hidden_name_set
, cmp = patchname_cmp
))