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 } |
