comparison src/org/tmatesoft/hg/core/HgChangesetFileSneaker.java @ 417:ccd7d25e5aea

New and better name for HgFileInformer - HgChangesetFileSneaker. Explain (comments) ties between HgManifest, HgDataFile, HgChangesetFileSneaker and reasons for method placement
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Thu, 22 Mar 2012 20:14:06 +0100
parents src/org/tmatesoft/hg/core/HgFileInformer.java@ee8264d80747
children 9c9c442b5f2e
comparison
equal deleted inserted replaced
416:d30083c80d52 417:ccd7d25e5aea
1 /*
2 * Copyright (c) 2011-2012 TMate Software Ltd
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 2 of the License.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * For information on how to redistribute this software under
14 * the terms of a license other than GNU General Public License
15 * contact TMate Software at support@hg4j.com
16 */
17 package org.tmatesoft.hg.core;
18
19 import org.tmatesoft.hg.internal.ManifestRevision;
20 import org.tmatesoft.hg.repo.HgDataFile;
21 import org.tmatesoft.hg.repo.HgManifest;
22 import org.tmatesoft.hg.repo.HgRepository;
23 import org.tmatesoft.hg.util.Path;
24 import org.tmatesoft.hg.util.Status;
25
26 /**
27 * Primary purpose is to provide information about file revisions at specific changeset. Multiple {@link #check(Path)} calls
28 * are possible once {@link #changeset(Nodeid)} (and optionally, {@link #followRenames(boolean)}) were set.
29 *
30 * <p>Sample:
31 * <pre><code>
32 * HgChangesetFileSneaker i = new HgChangesetFileSneaker(hgRepo).changeset(Nodeid.fromString("<40 digits>")).followRenames(true);
33 * if (i.check(file)) {
34 * HgCatCommand catCmd = new HgCatCommand(hgRepo).revision(i.getFileRevision());
35 * catCmd.execute(...);
36 * ...
37 * }
38 * </pre></code>
39 *
40 * TODO may add #manifest(Nodeid) to select manifest according to its revision (not only changeset revision as it's now)
41 *
42 * <p>Unlike {@link HgManifest#getFileRevision(int, Path)}, this class is useful when few files from the same changeset have to be inspected
43 *
44 * @see HgManifest#getFileRevision(int, Path)
45 * @author Artem Tikhomirov
46 * @author TMate Software Ltd.
47 */
48 public class HgChangesetFileSneaker {// TODO mark final once HgFileInformer gone
49
50 private final HgRepository repo;
51 private boolean followRenames;
52 private Nodeid cset;
53 private ManifestRevision cachedManifest;
54 private HgFileRevision fileRevision;
55 private boolean renamed;
56 private Status checkResult;
57
58 public HgChangesetFileSneaker(HgRepository hgRepo) {
59 repo = hgRepo;
60 }
61
62 /**
63 * Select specific changelog revision
64 *
65 * @param nid changeset identifier
66 * @return <code>this</code> for convenience
67 */
68 public HgChangesetFileSneaker changeset(Nodeid nid) {
69 if (nid == null || nid.isNull()) {
70 throw new IllegalArgumentException();
71 }
72 cset = nid;
73 cachedManifest = null;
74 fileRevision = null;
75 return this;
76 }
77
78 /**
79 * Whether to check file origins, default is false (look up only the name supplied)
80 *
81 * @param follow <code>true</code> to check copy/rename origin of the file if it is a copy.
82 * @return <code>this</code> for convenience
83 */
84 public HgChangesetFileSneaker followRenames(boolean follow) {
85 followRenames = follow;
86 fileRevision = null;
87 return this;
88 }
89
90 /**
91 * Shortcut to perform {@link #check(Path)} and {@link #exists()}. Result of the check may be accessed via {@link #getCheckStatus()}.
92 *
93 * @param file name of the file in question
94 * @return <code>true</code> if file is known at the selected changeset.
95 * @throws IllegalArgumentException if {@link #changeset(Nodeid)} not specified or file argument is bad.
96 * @throws HgInvalidControlFileException if access to revlog index/data entry failed
97 */
98 public boolean checkExists(Path file) throws HgInvalidControlFileException {
99 check(file);
100 if (!checkResult.isOk() && checkResult.getException() instanceof HgInvalidControlFileException) {
101 throw (HgInvalidControlFileException) checkResult.getException();
102 }
103 return checkResult.isOk() && exists();
104 }
105
106 /**
107 * Find file (or its origin, if {@link #followRenames(boolean)} was set to <code>true</code>) among files known at specified {@link #changeset(Nodeid)}.
108 *
109 * @param file name of the file in question
110 * @return status object that describes outcome, {@link Status#isOk() Ok} status indicates successful completion of the operation, but doesn't imply
111 * file existence, use {@link #exists()} for that purpose. Message of the status may provide further hints on what exactly had happened.
112 * @throws IllegalArgumentException if {@link #changeset(Nodeid)} not specified or file argument is bad.
113 */
114 public Status check(Path file) {
115 fileRevision = null;
116 checkResult = null;
117 renamed = false;
118 if (cset == null || file == null || file.isDirectory()) {
119 throw new IllegalArgumentException();
120 }
121 HgDataFile dataFile = repo.getFileNode(file);
122 if (!dataFile.exists()) {
123 checkResult = new Status(Status.Kind.OK, String.format("File named %s is not known in the repository", file));
124 return checkResult;
125 }
126 Nodeid toExtract = null;
127 HgManifest.Flags extractRevFlags = null;
128 String phaseMsg = "Extract manifest revision failed";
129 try {
130 if (cachedManifest == null) {
131 int csetRev = repo.getChangelog().getRevisionIndex(cset);
132 cachedManifest = new ManifestRevision(null, null); // XXX how about context and cached manifest revisions
133 repo.getManifest().walk(csetRev, csetRev, cachedManifest);
134 // cachedManifest shall be meaningful - changelog.getRevisionIndex() above ensures we've got version that exists.
135 }
136 toExtract = cachedManifest.nodeid(file);
137 extractRevFlags = cachedManifest.flags(file);
138 phaseMsg = "Follow copy/rename failed";
139 if (toExtract == null && followRenames) {
140 while (toExtract == null && dataFile.isCopy()) {
141 renamed = true;
142 file = dataFile.getCopySourceName();
143 dataFile = repo.getFileNode(file);
144 toExtract = cachedManifest.nodeid(file);
145 extractRevFlags = cachedManifest.flags(file);
146 }
147 }
148 } catch (HgException ex) {
149 checkResult = new Status(Status.Kind.ERROR, phaseMsg, ex);
150 return checkResult;
151 }
152 if (toExtract != null) {
153 fileRevision = new HgFileRevision(repo, toExtract, extractRevFlags, dataFile.getPath());
154 checkResult = new Status(Status.Kind.OK, String.format("File %s, revision %s found at changeset %s", dataFile.getPath(), toExtract.shortNotation(), cset.shortNotation()));
155 return checkResult;
156 }
157 checkResult = new Status(Status.Kind.OK, String.format("File %s nor its origins were known at repository %s revision", file, cset.shortNotation()));
158 return checkResult;
159 }
160
161 /**
162 * Re-get latest check status object
163 */
164 public Status getCheckStatus() {
165 assertCheckRan();
166 return checkResult;
167 }
168
169 /**
170 * @return result of the last {@link #check(Path)} call.
171 */
172 public boolean exists() {
173 assertCheckRan();
174 return fileRevision != null;
175 }
176
177 /**
178 * @return <code>true</code> if checked file was known by another name at the time of specified changeset.
179 */
180 public boolean hasAnotherName() {
181 assertCheckRan();
182 return renamed;
183 }
184
185 /**
186 * @return holder for file revision information
187 */
188 public HgFileRevision getFileRevision() {
189 assertCheckRan();
190 return fileRevision;
191 }
192
193 /**
194 * Name of the checked file as it was known at the time of the specified changeset.
195 *
196 * @return handy shortcut for <code>getFileRevision().getPath()</code>
197 */
198 public Path filename() {
199 assertCheckRan();
200 return fileRevision.getPath();
201 }
202
203 /**
204 * Revision of the checked file
205 *
206 * @return handy shortcut for <code>getFileRevision().getRevision()</code>
207 */
208 public Nodeid revision() {
209 assertCheckRan();
210 return fileRevision.getRevision();
211 }
212
213 private void assertCheckRan() {
214 if (checkResult == null) {
215 throw new HgBadStateException("Shall invoke #check(Path) first");
216 }
217 }
218
219 }