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 File directory = db.getDirectory();
121 String absolutePath = directory == null ? "null"
122 : directory.getAbsolutePath();
123 errw.println(
124 MessageFormat.format(CLIText.get().fatalThisProgramWillDestroyTheRepository
125 , absolutePath, REALLY));
126 throw die(CLIText.get().needApprovalToDestroyCurrentRepository);
127 }
128 if (!refList.isFile())
129 throw die(MessageFormat.format(CLIText.get().noSuchFile, refList.getPath()));
130 if (!graph.isFile())
131 throw die(MessageFormat.format(CLIText.get().noSuchFile, graph.getPath()));
132
133 recreateCommitGraph();
134 detachHead();
135 deleteAllRefs();
136 recreateRefs();
137 }
138
139 private void recreateCommitGraph() throws IOException {
140 final Map<ObjectId, ToRewrite> toRewrite = new HashMap<ObjectId, ToRewrite>();
141 List<ToRewrite> queue = new ArrayList<ToRewrite>();
142 try (RevWalk rw = new RevWalk(db);
143 final BufferedReader br = new BufferedReader(
144 new InputStreamReader(new FileInputStream(graph),
145 Constants.CHARSET))) {
146 String line;
147 while ((line = br.readLine()) != null) {
148 final String[] parts = line.split("[ \t]{1,}");
149 final ObjectId oldId = ObjectId.fromString(parts[0]);
150 try {
151 rw.parseCommit(oldId);
152
153 continue;
154 } catch (MissingObjectException mue) {
155
156 }
157
158 final long time = Long.parseLong(parts[1]) * 1000L;
159 final ObjectId[] parents = new ObjectId[parts.length - 2];
160 for (int i = 0; i < parents.length; i++) {
161 parents[i] = ObjectId.fromString(parts[2 + i]);
162 }
163
164 final ToRewrite t = new ToRewrite(oldId, time, parents);
165 toRewrite.put(oldId, t);
166 queue.add(t);
167 }
168 }
169
170 pm.beginTask("Rewriting commits", queue.size());
171 try (ObjectInserter oi = db.newObjectInserter()) {
172 final ObjectId emptyTree = oi.insert(Constants.OBJ_TREE,
173 new byte[] {});
174 final PersonIdent me = new PersonIdent("jgit rebuild-commitgraph",
175 "rebuild-commitgraph@localhost");
176 while (!queue.isEmpty()) {
177 final ListIterator<ToRewrite> itr = queue
178 .listIterator(queue.size());
179 queue = new ArrayList<ToRewrite>();
180 REWRITE: while (itr.hasPrevious()) {
181 final ToRewrite t = itr.previous();
182 final ObjectId[] newParents = new ObjectId[t.oldParents.length];
183 for (int k = 0; k < t.oldParents.length; k++) {
184 final ToRewrite p = toRewrite.get(t.oldParents[k]);
185 if (p != null) {
186 if (p.newId == null) {
187
188
189 queue.add(t);
190 continue REWRITE;
191 } else {
192 newParents[k] = p.newId;
193 }
194 } else {
195
196
197 newParents[k] = t.oldParents[k];
198 }
199 }
200
201 final CommitBuilder newc = new CommitBuilder();
202 newc.setTreeId(emptyTree);
203 newc.setAuthor(new PersonIdent(me, new Date(t.commitTime)));
204 newc.setCommitter(newc.getAuthor());
205 newc.setParentIds(newParents);
206 newc.setMessage("ORIGINAL " + t.oldId.name() + "\n");
207 t.newId = oi.insert(newc);
208 rewrites.put(t.oldId, t.newId);
209 pm.update(1);
210 }
211 }
212 oi.flush();
213 }
214 pm.endTask();
215 }
216
217 private static class ToRewrite {
218 final ObjectId oldId;
219
220 final long commitTime;
221
222 final ObjectId[] oldParents;
223
224 ObjectId newId;
225
226 ToRewrite(final ObjectId o, final long t, final ObjectId[] p) {
227 oldId = o;
228 commitTime = t;
229 oldParents = p;
230 }
231 }
232
233 private void detachHead() throws IOException {
234 final String head = db.getFullBranch();
235 final ObjectId id = db.resolve(Constants.HEAD);
236 if (!ObjectId.isId(head) && id != null) {
237 final LockFile lf;
238 lf = new LockFile(new File(db.getDirectory(), Constants.HEAD));
239 if (!lf.lock())
240 throw new IOException(MessageFormat.format(CLIText.get().cannotLock, Constants.HEAD));
241 lf.write(id);
242 if (!lf.commit())
243 throw new IOException(CLIText.get().cannotDeatchHEAD);
244 }
245 }
246
247 private void deleteAllRefs() throws Exception {
248 final RevWalk rw = new RevWalk(db);
249 Map<String, Ref> refs = db.getRefDatabase().getRefs(ALL);
250 for (final Ref r : refs.values()) {
251 if (Constants.HEAD.equals(r.getName()))
252 continue;
253 final RefUpdate u = db.updateRef(r.getName());
254 u.setForceUpdate(true);
255 u.delete(rw);
256 }
257 }
258
259 private void recreateRefs() throws Exception {
260 final Map<String, Ref> refs = computeNewRefs();
261 new RefWriter(refs.values()) {
262 @Override
263 protected void writeFile(final String name, final byte[] content)
264 throws IOException {
265 final File file = new File(db.getDirectory(), name);
266 final LockFile lck = new LockFile(file);
267 if (!lck.lock())
268 throw new ObjectWritingException(MessageFormat.format(CLIText.get().cantWrite, file));
269 try {
270 lck.write(content);
271 } catch (IOException ioe) {
272 throw new ObjectWritingException(MessageFormat.format(CLIText.get().cantWrite, file));
273 }
274 if (!lck.commit())
275 throw new ObjectWritingException(MessageFormat.format(CLIText.get().cantWrite, file));
276 }
277 }.writePackedRefs();
278 }
279
280 private Map<String, Ref> computeNewRefs() throws IOException {
281 final Map<String, Ref> refs = new HashMap<String, Ref>();
282 try (RevWalk rw = new RevWalk(db);
283 BufferedReader br = new BufferedReader(
284 new InputStreamReader(new FileInputStream(refList),
285 Constants.CHARSET))) {
286 String line;
287 while ((line = br.readLine()) != null) {
288 final String[] parts = line.split("[ \t]{1,}");
289 final ObjectId origId = ObjectId.fromString(parts[0]);
290 final String type = parts[1];
291 final String name = parts[2];
292
293 ObjectId id = rewrites.get(origId);
294 if (id == null)
295 id = origId;
296 try {
297 rw.parseAny(id);
298 } catch (MissingObjectException mue) {
299 if (!Constants.TYPE_COMMIT.equals(type)) {
300 errw.println(MessageFormat.format(CLIText.get().skippingObject, type, name));
301 continue;
302 }
303 throw new MissingObjectException(id, type);
304 }
305 refs.put(name, new ObjectIdRef.Unpeeled(Ref.Storage.PACKED,
306 name, id));
307 }
308 }
309 return refs;
310 }
311 }