comparison src/org/tmatesoft/hg/internal/CommitFacility.java @ 617:65c01508f002

Rollback support for commands that modify repository. Strategy to keep complete copy of a file being changed
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Wed, 15 May 2013 20:10:09 +0200
parents 5e0313485eef
children 7c0d2ce340b8
comparison
equal deleted inserted replaced
616:5e0313485eef 617:65c01508f002
17 package org.tmatesoft.hg.internal; 17 package org.tmatesoft.hg.internal;
18 18
19 import static org.tmatesoft.hg.repo.HgRepository.DEFAULT_BRANCH_NAME; 19 import static org.tmatesoft.hg.repo.HgRepository.DEFAULT_BRANCH_NAME;
20 import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION; 20 import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION;
21 import static org.tmatesoft.hg.repo.HgRepositoryFiles.Branch; 21 import static org.tmatesoft.hg.repo.HgRepositoryFiles.Branch;
22 import static org.tmatesoft.hg.repo.HgRepositoryFiles.UndoBranch;
22 import static org.tmatesoft.hg.util.LogFacility.Severity.Error; 23 import static org.tmatesoft.hg.util.LogFacility.Severity.Error;
23 24
24 import java.io.File; 25 import java.io.File;
25 import java.io.FileOutputStream; 26 import java.io.FileOutputStream;
26 import java.io.IOException; 27 import java.io.IOException;
92 93
93 public void user(String userName) { 94 public void user(String userName) {
94 user = userName; 95 user = userName;
95 } 96 }
96 97
97 public Nodeid commit(String message) throws HgIOException, HgRepositoryLockException { 98 // this method doesn't roll transaction back in case of failure, caller's responsibility
99 // this method expects repository to be locked, if needed
100 public Nodeid commit(String message, Transaction transaction) throws HgIOException, HgRepositoryLockException {
98 final HgChangelog clog = repo.getRepo().getChangelog(); 101 final HgChangelog clog = repo.getRepo().getChangelog();
99 final int clogRevisionIndex = clog.getRevisionCount(); 102 final int clogRevisionIndex = clog.getRevisionCount();
100 ManifestRevision c1Manifest = new ManifestRevision(null, null); 103 ManifestRevision c1Manifest = new ManifestRevision(null, null);
101 ManifestRevision c2Manifest = new ManifestRevision(null, null); 104 ManifestRevision c2Manifest = new ManifestRevision(null, null);
102 final Nodeid p1Cset = p1Commit == NO_REVISION ? null : clog.getRevision(p1Commit); 105 final Nodeid p1Cset = p1Commit == NO_REVISION ? null : clog.getRevision(p1Commit);
159 newlyAddedFiles.put(df.getPath(), contentStream); 162 newlyAddedFiles.put(df.getPath(), contentStream);
160 // FIXME df doesn't get df.content updated, and clients 163 // FIXME df doesn't get df.content updated, and clients
161 // that would attempt to access newly added file after commit would fail 164 // that would attempt to access newly added file after commit would fail
162 // (despite the fact the file is in there) 165 // (despite the fact the file is in there)
163 } 166 }
164 RevlogStreamWriter fileWriter = new RevlogStreamWriter(repo, contentStream); 167 RevlogStreamWriter fileWriter = new RevlogStreamWriter(repo, contentStream, transaction);
165 Nodeid fileRev = fileWriter.addRevision(bac.toArray(), clogRevisionIndex, fp.first(), fp.second()); 168 Nodeid fileRev = fileWriter.addRevision(bac.toArray(), clogRevisionIndex, fp.first(), fp.second());
166 newManifestRevision.put(df.getPath(), fileRev); 169 newManifestRevision.put(df.getPath(), fileRev);
167 touchInDirstate.add(df.getPath()); 170 touchInDirstate.add(df.getPath());
168 } 171 }
169 // 172 //
170 // Manifest 173 // Manifest
171 final ManifestEntryBuilder manifestBuilder = new ManifestEntryBuilder(); 174 final ManifestEntryBuilder manifestBuilder = new ManifestEntryBuilder();
172 for (Map.Entry<Path, Nodeid> me : newManifestRevision.entrySet()) { 175 for (Map.Entry<Path, Nodeid> me : newManifestRevision.entrySet()) {
173 manifestBuilder.add(me.getKey().toString(), me.getValue()); 176 manifestBuilder.add(me.getKey().toString(), me.getValue());
174 } 177 }
175 RevlogStreamWriter manifestWriter = new RevlogStreamWriter(repo, repo.getImplAccess().getManifestStream()); 178 RevlogStreamWriter manifestWriter = new RevlogStreamWriter(repo, repo.getImplAccess().getManifestStream(), transaction);
176 Nodeid manifestRev = manifestWriter.addRevision(manifestBuilder.build(), clogRevisionIndex, manifestParents.first(), manifestParents.second()); 179 Nodeid manifestRev = manifestWriter.addRevision(manifestBuilder.build(), clogRevisionIndex, manifestParents.first(), manifestParents.second());
177 // 180 //
178 // Changelog 181 // Changelog
179 final ChangelogEntryBuilder changelogBuilder = new ChangelogEntryBuilder(); 182 final ChangelogEntryBuilder changelogBuilder = new ChangelogEntryBuilder();
180 changelogBuilder.setModified(files.keySet()); 183 changelogBuilder.setModified(files.keySet());
181 changelogBuilder.branch(branch == null ? DEFAULT_BRANCH_NAME : branch); 184 changelogBuilder.branch(branch == null ? DEFAULT_BRANCH_NAME : branch);
182 changelogBuilder.user(String.valueOf(user)); 185 changelogBuilder.user(String.valueOf(user));
183 byte[] clogContent = changelogBuilder.build(manifestRev, message); 186 byte[] clogContent = changelogBuilder.build(manifestRev, message);
184 RevlogStreamWriter changelogWriter = new RevlogStreamWriter(repo, repo.getImplAccess().getChangelogStream()); 187 RevlogStreamWriter changelogWriter = new RevlogStreamWriter(repo, repo.getImplAccess().getChangelogStream(), transaction);
185 Nodeid changesetRev = changelogWriter.addRevision(clogContent, clogRevisionIndex, p1Commit, p2Commit); 188 Nodeid changesetRev = changelogWriter.addRevision(clogContent, clogRevisionIndex, p1Commit, p2Commit);
186 // TODO move fncache update to an external facility, along with dirstate and bookmark update 189 // TODO move fncache update to an external facility, along with dirstate and bookmark update
187 if (!newlyAddedFiles.isEmpty() && repo.fncacheInUse()) { 190 if (!newlyAddedFiles.isEmpty() && repo.fncacheInUse()) {
188 FNCacheFile fncache = new FNCacheFile(repo); 191 FNCacheFile fncache = new FNCacheFile(repo);
189 for (Path p : newlyAddedFiles.keySet()) { 192 for (Path p : newlyAddedFiles.keySet()) {
199 repo.getLog().dump(getClass(), Error, ex, "Failed to write fncache, error ignored"); 202 repo.getLog().dump(getClass(), Error, ex, "Failed to write fncache, error ignored");
200 } 203 }
201 } 204 }
202 String oldBranchValue = DirstateReader.readBranch(repo); 205 String oldBranchValue = DirstateReader.readBranch(repo);
203 String newBranchValue = branch == null ? DEFAULT_BRANCH_NAME : branch; 206 String newBranchValue = branch == null ? DEFAULT_BRANCH_NAME : branch;
207 // TODO undo.dirstate and undo.branch as described in http://mercurial.selenic.com/wiki/FileFormats#undo..2A
204 if (!oldBranchValue.equals(newBranchValue)) { 208 if (!oldBranchValue.equals(newBranchValue)) {
205 File branchFile = repo.getRepositoryFile(Branch); 209 File branchFile = transaction.prepare(repo.getRepositoryFile(Branch), repo.getRepositoryFile(UndoBranch));
206 FileOutputStream fos = null; 210 FileOutputStream fos = null;
207 try { 211 try {
208 fos = new FileOutputStream(branchFile); 212 fos = new FileOutputStream(branchFile);
209 fos.write(newBranchValue.getBytes(EncodingHelper.getUTF8())); 213 fos.write(newBranchValue.getBytes(EncodingHelper.getUTF8()));
210 fos.flush(); 214 fos.flush();
215 fos.close();
216 fos = null;
217 transaction.done(branchFile);
211 } catch (IOException ex) { 218 } catch (IOException ex) {
219 transaction.failure(branchFile, ex);
212 repo.getLog().dump(getClass(), Error, ex, "Failed to write branch information, error ignored"); 220 repo.getLog().dump(getClass(), Error, ex, "Failed to write branch information, error ignored");
213 } finally { 221 } finally {
214 try { 222 try {
215 if (fos != null) { 223 if (fos != null) {
216 fos.close(); 224 fos.close();
218 } catch (IOException ex) { 226 } catch (IOException ex) {
219 repo.getLog().dump(getClass(), Error, ex, null); 227 repo.getLog().dump(getClass(), Error, ex, null);
220 } 228 }
221 } 229 }
222 } 230 }
223 // bring dirstate up to commit state 231 // bring dirstate up to commit state, TODO share this code with HgAddRemoveCommand
224 final DirstateBuilder dirstateBuilder = new DirstateBuilder(repo); 232 final DirstateBuilder dirstateBuilder = new DirstateBuilder(repo);
225 dirstateBuilder.fillFrom(new DirstateReader(repo, new Path.SimpleSource())); 233 dirstateBuilder.fillFrom(new DirstateReader(repo, new Path.SimpleSource()));
226 for (Path p : removals) { 234 for (Path p : removals) {
227 dirstateBuilder.recordRemoved(p); 235 dirstateBuilder.recordRemoved(p);
228 } 236 }
229 for (Path p : touchInDirstate) { 237 for (Path p : touchInDirstate) {
230 dirstateBuilder.recordUncertain(p); 238 dirstateBuilder.recordUncertain(p);
231 } 239 }
232 dirstateBuilder.parents(changesetRev, Nodeid.NULL); 240 dirstateBuilder.parents(changesetRev, Nodeid.NULL);
233 dirstateBuilder.serialize(); 241 dirstateBuilder.serialize(transaction);
234 // update bookmarks 242 // update bookmarks
235 if (p1Commit != NO_REVISION || p2Commit != NO_REVISION) { 243 if (p1Commit != NO_REVISION || p2Commit != NO_REVISION) {
236 repo.getRepo().getBookmarks().updateActive(p1Cset, p2Cset, changesetRev); 244 repo.getRepo().getBookmarks().updateActive(p1Cset, p2Cset, changesetRev);
237 } 245 }
238 // TODO undo.dirstate and undo.branch as described in http://mercurial.selenic.com/wiki/FileFormats#undo..2A
239 // TODO Revisit: might be reasonable to send out a "Repo changed" notification, to clear 246 // TODO Revisit: might be reasonable to send out a "Repo changed" notification, to clear
240 // e.g. cached branch, tags and so on, not to rely on file change detection methods? 247 // e.g. cached branch, tags and so on, not to rely on file change detection methods?
241 // The same notification might come useful once Pull is implemented 248 // The same notification might come useful once Pull is implemented
242 return changesetRev; 249 return changesetRev;
243 } 250 }