Mercurial > jhg
comparison src/org/tmatesoft/hg/core/LogCommand.java @ 80:4222b04f34ee
Follow history of a file
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Tue, 25 Jan 2011 03:54:32 +0100 |
parents | c677e1593919 |
children | 61eedab3eb3e |
comparison
equal
deleted
inserted
replaced
79:5f9635c01681 | 80:4222b04f34ee |
---|---|
25 import java.util.List; | 25 import java.util.List; |
26 import java.util.Set; | 26 import java.util.Set; |
27 import java.util.TreeSet; | 27 import java.util.TreeSet; |
28 | 28 |
29 import org.tmatesoft.hg.repo.Changeset; | 29 import org.tmatesoft.hg.repo.Changeset; |
30 import org.tmatesoft.hg.repo.HgDataFile; | |
30 import org.tmatesoft.hg.repo.HgRepository; | 31 import org.tmatesoft.hg.repo.HgRepository; |
31 import org.tmatesoft.hg.repo.StatusCollector; | 32 import org.tmatesoft.hg.repo.StatusCollector; |
32 import org.tmatesoft.hg.util.PathPool; | 33 import org.tmatesoft.hg.util.PathPool; |
33 | 34 |
34 | 35 |
49 private int limit = 0, count = 0; | 50 private int limit = 0, count = 0; |
50 private int startRev = 0, endRev = TIP; | 51 private int startRev = 0, endRev = TIP; |
51 private Handler delegate; | 52 private Handler delegate; |
52 private Calendar date; | 53 private Calendar date; |
53 private Path file; | 54 private Path file; |
55 private boolean followHistory; // makes sense only when file != null | |
54 private Cset changeset; | 56 private Cset changeset; |
55 | 57 |
56 public LogCommand(HgRepository hgRepo) { | 58 public LogCommand(HgRepository hgRepo) { |
57 this.repo = hgRepo; | 59 this.repo = hgRepo; |
58 } | 60 } |
59 | 61 |
60 /** | 62 /** |
131 } | 133 } |
132 | 134 |
133 /** | 135 /** |
134 * Visit history of a given file only. | 136 * Visit history of a given file only. |
135 * @param file path relative to repository root. Pass <code>null</code> to reset. | 137 * @param file path relative to repository root. Pass <code>null</code> to reset. |
136 */ | 138 * @param followCopyRename true to report changesets of the original file(-s), if copy/rename ever occured to the file. |
137 public LogCommand file(Path file) { | 139 */ |
140 public LogCommand file(Path file, boolean followCopyRename) { | |
138 // multiple? Bad idea, would need to include extra method into Handler to tell start of next file | 141 // multiple? Bad idea, would need to include extra method into Handler to tell start of next file |
139 // implicit --follow in this case | |
140 this.file = file; | 142 this.file = file; |
143 followHistory = followCopyRename; | |
141 return this; | 144 return this; |
142 } | 145 } |
143 | 146 |
144 /** | 147 /** |
145 * Similar to {@link #execute(org.tmatesoft.hg.repo.Changeset.Inspector)}, collects and return result as a list. | 148 * Similar to {@link #execute(org.tmatesoft.hg.repo.Changeset.Inspector)}, collects and return result as a list. |
168 count = 0; | 171 count = 0; |
169 changeset = new Cset(new StatusCollector(repo), new PathPool(repo.getPathHelper())); | 172 changeset = new Cset(new StatusCollector(repo), new PathPool(repo.getPathHelper())); |
170 if (file == null) { | 173 if (file == null) { |
171 repo.getChangelog().range(startRev, endRev, this); | 174 repo.getChangelog().range(startRev, endRev, this); |
172 } else { | 175 } else { |
173 repo.getFileNode(file).history(startRev, endRev, this); | 176 HgDataFile fileNode = repo.getFileNode(file); |
177 fileNode.history(startRev, endRev, this); | |
178 if (handler instanceof FileHistoryHandler && fileNode.isCopy()) { | |
179 // even if we do not follow history, report file rename | |
180 do { | |
181 FileRevision src = new FileRevision(repo, fileNode.getCopySourceRevision(), fileNode.getCopySourceName()); | |
182 FileRevision dst = new FileRevision(repo, fileNode.getRevisionNumber(0), fileNode.getPath()); | |
183 ((FileHistoryHandler) handler).copy(src, dst); | |
184 if (limit > 0 && count >= limit) { | |
185 // if limit reach, follow is useless. | |
186 break; | |
187 } | |
188 if (followHistory) { | |
189 fileNode = repo.getFileNode(src.getPath()); | |
190 fileNode.history(this); | |
191 } | |
192 } while (followHistory && fileNode.isCopy()); | |
193 } | |
174 } | 194 } |
175 } finally { | 195 } finally { |
176 delegate = null; | 196 delegate = null; |
177 changeset = null; | 197 changeset = null; |
178 } | 198 } |
213 * @param changeset not necessarily a distinct instance each time, {@link Cset#clone() clone()} if need a copy. | 233 * @param changeset not necessarily a distinct instance each time, {@link Cset#clone() clone()} if need a copy. |
214 */ | 234 */ |
215 void next(Cset changeset); | 235 void next(Cset changeset); |
216 } | 236 } |
217 | 237 |
238 /** | |
239 * When {@link LogCommand} is executed against file, handler passed to {@link LogCommand#execute(Handler)} may optionally | |
240 * implement this interface to get information about file renames. Method {@link #copy(FileRevision, FileRevision)} would | |
241 * get invoked prior any changeset of the original file (if file history being followed) is reported via {@link #next(Cset)}. | |
242 * | |
243 * For {@link LogCommand#file(Path, boolean)} with renamed file path and follow argument set to false, | |
244 * {@link #copy(FileRevision, FileRevision)} would be invoked for the first copy/rename in the history of the file, but not | |
245 * followed by any changesets. | |
246 * | |
247 * @author Artem Tikhomirov | |
248 * @author TMate Software Ltd. | |
249 */ | |
250 public interface FileHistoryHandler extends Handler { | |
251 // XXX perhaps, should distinguish copy from rename? And what about merged revisions and following them? | |
252 void copy(FileRevision from, FileRevision to); | |
253 } | |
254 | |
218 public static class CollectHandler implements Handler { | 255 public static class CollectHandler implements Handler { |
219 private final List<Cset> result = new LinkedList<Cset>(); | 256 private final List<Cset> result = new LinkedList<Cset>(); |
220 | 257 |
221 public List<Cset> getChanges() { | 258 public List<Cset> getChanges() { |
222 return Collections.unmodifiableList(result); | 259 return Collections.unmodifiableList(result); |
230 public static final class FileRevision { | 267 public static final class FileRevision { |
231 private final HgRepository repo; | 268 private final HgRepository repo; |
232 private final Nodeid revision; | 269 private final Nodeid revision; |
233 private final Path path; | 270 private final Path path; |
234 | 271 |
235 public FileRevision(HgRepository hgRepo, Nodeid rev, Path p) { | 272 /*package-local*/FileRevision(HgRepository hgRepo, Nodeid rev, Path p) { |
236 if (hgRepo == null || rev == null || p == null) { | 273 if (hgRepo == null || rev == null || p == null) { |
237 throw new IllegalArgumentException(); | 274 throw new IllegalArgumentException(); |
238 } | 275 } |
239 repo = hgRepo; | 276 repo = hgRepo; |
240 revision = rev; | 277 revision = rev; |
247 public Nodeid getRevision() { | 284 public Nodeid getRevision() { |
248 return revision; | 285 return revision; |
249 } | 286 } |
250 public byte[] getContent() { | 287 public byte[] getContent() { |
251 // XXX Content wrapper, to allow formats other than byte[], e.g. Stream, DataAccess, etc? | 288 // XXX Content wrapper, to allow formats other than byte[], e.g. Stream, DataAccess, etc? |
252 return repo.getFileNode(path).content(); | 289 return repo.getFileNode(path).content(revision); |
253 } | 290 } |
254 } | 291 } |
255 } | 292 } |