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