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