comparison src/org/tmatesoft/hg/internal/COWTransaction.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
children 99ad1e3a4e4d
comparison
equal deleted inserted replaced
616:5e0313485eef 617:65c01508f002
1 /*
2 * Copyright (c) 2013 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.internal;
18
19 import java.io.File;
20 import java.io.IOException;
21 import java.util.Iterator;
22 import java.util.LinkedList;
23 import java.util.List;
24
25 import org.tmatesoft.hg.core.HgIOException;
26 import org.tmatesoft.hg.core.SessionContext;
27
28 /**
29 * This transaction strategy makes a copy of original file and breaks origin hard links, if any.
30 * Changes are directed to actual repository files.
31 *
32 * On commit, remove all backup copies
33 * On rollback, move all backup files in place of original
34 *
35 * @author Artem Tikhomirov
36 * @author TMate Software Ltd.
37 */
38 public final class COWTransaction extends Transaction {
39
40 private final FileUtils fileHelper;
41 private final List<RollbackEntry> entries = new LinkedList<RollbackEntry>();
42
43 public COWTransaction(SessionContext.Source ctxSource) {
44 fileHelper = new FileUtils(ctxSource.getSessionContext().getLog());
45 }
46
47 @Override
48 public File prepare(File f) throws HgIOException {
49 if (!f.exists()) {
50 record(f, null);
51 return f;
52 }
53 if (known(f)) {
54 return f;
55 }
56 final File parentDir = f.getParentFile();
57 assert parentDir.canWrite();
58 File copy = new File(parentDir, f.getName() + ".hg4j.copy");
59 fileHelper.copy(f, copy);
60 final long lm = f.lastModified();
61 copy.setLastModified(lm);
62 File backup = new File(parentDir, f.getName() + ".hg4j.orig");
63 if (backup.exists()) {
64 backup.delete();
65 }
66 if (!f.renameTo(backup)) {
67 throw new HgIOException(String.format("Failed to backup %s to %s", f.getName(), backup.getName()), backup);
68 }
69 if (!copy.renameTo(f)) {
70 throw new HgIOException(String.format("Failed to bring on-write copy in place (%s to %s)", copy.getName(), f.getName()), copy);
71 }
72 f.setLastModified(lm);
73 record(f, backup);
74 return f;
75 }
76
77 @Override
78 public File prepare(File origin, File backup) throws HgIOException {
79 if (known(origin)) {
80 return origin;
81 }
82 fileHelper.copy(origin, backup);
83 final RollbackEntry e = record(origin, backup);
84 e.keepBackup = true;
85 return origin;
86 }
87
88 @Override
89 public void done(File f) throws HgIOException {
90 find(f).success = true;
91 }
92
93 @Override
94 public void failure(File f, IOException ex) {
95 find(f).failure = ex;
96 }
97
98 // XXX custom exception for commit and rollback to hold information about files rolled back
99
100 @Override
101 public void commit() throws HgIOException {
102 for (Iterator<RollbackEntry> it = entries.iterator(); it.hasNext();) {
103 RollbackEntry e = it.next();
104 assert e.success;
105 if (e.failure != null) {
106 throw new HgIOException("Can't close transaction with a failure.", e.failure, e.origin);
107 }
108 if (!e.keepBackup && e.backup != null) {
109 e.backup.delete();
110 }
111 it.remove();
112 }
113 }
114
115 @Override
116 public void rollback() throws HgIOException {
117 LinkedList<RollbackEntry> success = new LinkedList<RollbackEntry>();
118 for (Iterator<RollbackEntry> it = entries.iterator(); it.hasNext();) {
119 RollbackEntry e = it.next();
120 e.origin.delete();
121 if (e.backup != null) {
122 if (!e.backup.renameTo(e.origin)) {
123 String msg = String.format("Transaction rollback failed, could not rename backup %s back to %s", e.backup.getName(), e.origin.getName());
124 throw new HgIOException(msg, e.origin);
125 }
126 }
127 success.add(e);
128 it.remove();
129 }
130 }
131
132 private RollbackEntry record(File origin, File backup) {
133 final RollbackEntry e = new RollbackEntry(origin, backup);
134 entries.add(e);
135 return e;
136 }
137
138 private boolean known(File f) {
139 for (RollbackEntry e : entries) {
140 if (e.origin.equals(f)) {
141 return true;
142 }
143 }
144 return false;
145 }
146 private RollbackEntry find(File f) {
147 for (RollbackEntry e : entries) {
148 if (e.origin.equals(f)) {
149 return e;
150 }
151 }
152 assert false;
153 return new RollbackEntry(f,f);
154 }
155
156 private static class RollbackEntry {
157 public final File origin;
158 public final File backup; // may be null to indicate file didn't exist
159 public boolean success = false;
160 public IOException failure = null;
161 public boolean keepBackup = false;
162
163 public RollbackEntry(File o, File b) {
164 origin = o;
165 backup = b;
166 }
167 }
168
169 public static class Factory implements Transaction.Factory {
170
171 public Transaction create(SessionContext.Source ctxSource) {
172 return new COWTransaction(ctxSource);
173 }
174
175 }
176 }