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 }