1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 package org.eclipse.jgit.pgm.debug;
45
46 import static org.eclipse.jgit.lib.RefDatabase.ALL;
47
48 import java.io.BufferedReader;
49 import java.io.File;
50 import java.io.FileInputStream;
51 import java.io.IOException;
52 import java.io.InputStreamReader;
53 import java.text.MessageFormat;
54 import java.util.ArrayList;
55 import java.util.Date;
56 import java.util.HashMap;
57 import java.util.List;
58 import java.util.ListIterator;
59 import java.util.Map;
60
61 import org.eclipse.jgit.errors.MissingObjectException;
62 import org.eclipse.jgit.errors.ObjectWritingException;
63 import org.eclipse.jgit.internal.storage.file.LockFile;
64 import org.eclipse.jgit.lib.CommitBuilder;
65 import org.eclipse.jgit.lib.Constants;
66 import org.eclipse.jgit.lib.ObjectId;
67 import org.eclipse.jgit.lib.ObjectIdRef;
68 import org.eclipse.jgit.lib.ObjectInserter;
69 import org.eclipse.jgit.lib.PersonIdent;
70 import org.eclipse.jgit.lib.ProgressMonitor;
71 import org.eclipse.jgit.lib.Ref;
72 import org.eclipse.jgit.lib.RefUpdate;
73 import org.eclipse.jgit.lib.RefWriter;
74 import org.eclipse.jgit.lib.TextProgressMonitor;
75 import org.eclipse.jgit.pgm.Command;
76 import org.eclipse.jgit.pgm.TextBuiltin;
77 import org.eclipse.jgit.pgm.internal.CLIText;
78 import org.eclipse.jgit.revwalk.RevWalk;
79 import org.kohsuke.args4j.Argument;
80 import org.kohsuke.args4j.Option;
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100 @Command(usage = "usage_RebuildCommitGraph")
101 class RebuildCommitGraph extends TextBuiltin {
102 private static final String REALLY = "--destroy-this-repository";
103
104 @Option(name = REALLY, usage = "usage_approveDestructionOfRepository")
105 boolean really;
106
107 @Argument(index = 0, required = true, metaVar = "metaVar_refs", usage = "usage_forEachRefOutput")
108 File refList;
109
110 @Argument(index = 1, required = true, metaVar = "metaVar_refs", usage = "usage_logAllPretty")
111 File graph;
112
113 private final ProgressMonitor pm = new TextProgressMonitor(errw);
114
115 private Map<ObjectId, ObjectId> rewrites = new HashMap<ObjectId, ObjectId>();
116
117 @Override
118 protected void run() throws Exception {
119 if (!really && !db.getRefDatabase().getRefs(ALL).isEmpty()) {
120 errw.println(
121 MessageFormat.format(CLIText.get().fatalThisProgramWillDestroyTheRepository
122 , db.getDirectory().getAbsolutePath(), REALLY));
123 throw die(CLIText.get().needApprovalToDestroyCurrentRepository);
124 }
125 if (!refList.isFile())
126 throw die(MessageFormat.format(CLIText.get().noSuchFile, refList.getPath()));
127 if (!graph.isFile())
128 throw die(MessageFormat.format(CLIText.get().noSuchFile, graph.getPath()));
129
130 recreateCommitGraph();
131 detachHead();
132 deleteAllRefs();
133 recreateRefs();
134 }
135
136 private void recreateCommitGraph() throws IOException {
137 final Map<ObjectId, ToRewrite> toRewrite = new HashMap<ObjectId, ToRewrite>();
138 List<ToRewrite> queue = new ArrayList<ToRewrite>();
139 try (RevWalk rw = new RevWalk(db);
140 final BufferedReader br = new BufferedReader(
141 new InputStreamReader(new FileInputStream(graph),
142 Constants.CHARSET))) {
143 String line;
144 while ((line = br.readLine()) != null) {
145 final String[] parts = line.split("[ \t]{1,}");
146 final ObjectId oldId = ObjectId.fromString(parts[0]);
147 try {
148 rw.parseCommit(oldId);
149
150 continue;
151 } catch (MissingObjectException mue) {
152
153 }
154
155 final long time = Long.parseLong(parts[1]) * 1000L;
156 final ObjectId[] parents = new ObjectId[parts.length - 2];
157 for (int i = 0; i < parents.length; i++) {
158 parents[i] = ObjectId.fromString(parts[2 + i]);
159 }
160
161 final ToRewrite t = new ToRewrite(oldId, time, parents);
162 toRewrite.put(oldId, t);
163 queue.add(t);
164 }
165 }
166
167 pm.beginTask("Rewriting commits", queue.size());
168 try (ObjectInserter oi = db.newObjectInserter()) {
169 final ObjectId emptyTree = oi.insert(Constants.OBJ_TREE,
170 new byte[] {});
171 final PersonIdent me = new PersonIdent("jgit rebuild-commitgraph",
172 "rebuild-commitgraph@localhost");
173 while (!queue.isEmpty()) {
174 final ListIterator<ToRewrite> itr = queue
175 .listIterator(queue.size());
176 queue = new ArrayList<ToRewrite>();
177 REWRITE: while (itr.hasPrevious()) {
178 final ToRewrite t = itr.previous();
179 final ObjectId[] newParents = new ObjectId[t.oldParents.length];
180 for (int k = 0; k < t.oldParents.length; k++) {
181 final ToRewrite p = toRewrite.get(t.oldParents[k]);
182 if (p != null) {
183 if (p.newId == null) {
184
185
186 queue.add(t);
187 continue REWRITE;
188 } else {
189 newParents[k] = p.newId;
190 }
191 } else {
192
193
194 newParents[k] = t.oldParents[k];
195 }
196 }
197
198 final CommitBuilder newc = new CommitBuilder();
199 newc.setTreeId(emptyTree);
200 newc.setAuthor(new PersonIdent(me, new Date(t.commitTime)));
201 newc.setCommitter(newc.getAuthor());
202 newc.setParentIds(newParents);
203 newc.setMessage("ORIGINAL " + t.oldId.name() + "\n");
204 t.newId = oi.insert(newc);
205 rewrites.put(t.oldId, t.newId);
206 pm.update(1);
207 }
208 }
209 oi.flush();
210 }
211 pm.endTask();
212 }
213
214 private static class ToRewrite {
215 final ObjectId oldId;
216
217 final long commitTime;
218
219 final ObjectId[] oldParents;
220
221 ObjectId newId;
222
223 ToRewrite(final ObjectId o, final long t, final ObjectId[] p) {
224 oldId = o;
225 commitTime = t;
226 oldParents = p;
227 }
228 }
229
230 private void detachHead() throws IOException {
231 final String head = db.getFullBranch();
232 final ObjectId id = db.resolve(Constants.HEAD);
233 if (!ObjectId.isId(head) && id != null) {
234 final LockFile lf;
235 lf = new LockFile(new File(db.getDirectory(), Constants.HEAD), db.getFS());
236 if (!lf.lock())
237 throw new IOException(MessageFormat.format(CLIText.get().cannotLock, Constants.HEAD));
238 lf.write(id);
239 if (!lf.commit())
240 throw new IOException(CLIText.get().cannotDeatchHEAD);
241 }
242 }
243
244 private void deleteAllRefs() throws Exception {
245 final RevWalk rw = new RevWalk(db);
246 Map<String, Ref> refs = db.getRefDatabase().getRefs(ALL);
247 for (final Ref r : refs.values()) {
248 if (Constants.HEAD.equals(r.getName()))
249 continue;
250 final RefUpdate u = db.updateRef(r.getName());
251 u.setForceUpdate(true);
252 u.delete(rw);
253 }
254 }
255
256 private void recreateRefs() throws Exception {
257 final Map<String, Ref> refs = computeNewRefs();
258 new RefWriter(refs.values()) {
259 @Override
260 protected void writeFile(final String name, final byte[] content)
261 throws IOException {
262 final File file = new File(db.getDirectory(), name);
263 final LockFile lck = new LockFile(file, db.getFS());
264 if (!lck.lock())
265 throw new ObjectWritingException(MessageFormat.format(CLIText.get().cantWrite, file));
266 try {
267 lck.write(content);
268 } catch (IOException ioe) {
269 throw new ObjectWritingException(MessageFormat.format(CLIText.get().cantWrite, file));
270 }
271 if (!lck.commit())
272 throw new ObjectWritingException(MessageFormat.format(CLIText.get().cantWrite, file));
273 }
274 }.writePackedRefs();
275 }
276
277 private Map<String, Ref> computeNewRefs() throws IOException {
278 final Map<String, Ref> refs = new HashMap<String, Ref>();
279 try (RevWalk rw = new RevWalk(db);
280 BufferedReader br = new BufferedReader(
281 new InputStreamReader(new FileInputStream(refList),
282 Constants.CHARSET))) {
283 String line;
284 while ((line = br.readLine()) != null) {
285 final String[] parts = line.split("[ \t]{1,}");
286 final ObjectId origId = ObjectId.fromString(parts[0]);
287 final String type = parts[1];
288 final String name = parts[2];
289
290 ObjectId id = rewrites.get(origId);
291 if (id == null)
292 id = origId;
293 try {
294 rw.parseAny(id);
295 } catch (MissingObjectException mue) {
296 if (!Constants.TYPE_COMMIT.equals(type)) {
297 errw.println(MessageFormat.format(CLIText.get().skippingObject, type, name));
298 continue;
299 }
300 throw new MissingObjectException(id, type);
301 }
302 refs.put(name, new ObjectIdRef.Unpeeled(Ref.Storage.PACKED,
303 name, id));
304 }
305 }
306 return refs;
307 }
308 }