Mercurial > jhg
comparison src/org/tmatesoft/hg/internal/WorkingDirFileWriter.java @ 580:bd5926e24aa3
Respect unix flags for checkout/revert
| author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
|---|---|
| date | Fri, 19 Apr 2013 20:30:34 +0200 |
| parents | becd2a1310a2 |
| children | 6526d8adbc0f |
comparison
equal
deleted
inserted
replaced
| 579:36e36b926747 | 580:bd5926e24aa3 |
|---|---|
| 14 * the terms of a license other than GNU General Public License | 14 * the terms of a license other than GNU General Public License |
| 15 * contact TMate Software at support@hg4j.com | 15 * contact TMate Software at support@hg4j.com |
| 16 */ | 16 */ |
| 17 package org.tmatesoft.hg.internal; | 17 package org.tmatesoft.hg.internal; |
| 18 | 18 |
| 19 import static org.tmatesoft.hg.util.LogFacility.Severity.Warn; | |
| 20 | |
| 19 import java.io.File; | 21 import java.io.File; |
| 20 import java.io.FileOutputStream; | 22 import java.io.FileOutputStream; |
| 21 import java.io.IOException; | 23 import java.io.IOException; |
| 22 import java.nio.ByteBuffer; | 24 import java.nio.ByteBuffer; |
| 23 import java.nio.channels.FileChannel; | 25 import java.nio.channels.FileChannel; |
| 24 | 26 |
| 25 import org.tmatesoft.hg.repo.HgDataFile; | 27 import org.tmatesoft.hg.repo.HgDataFile; |
| 28 import org.tmatesoft.hg.repo.HgManifest; | |
| 26 import org.tmatesoft.hg.util.ByteChannel; | 29 import org.tmatesoft.hg.util.ByteChannel; |
| 27 import org.tmatesoft.hg.util.CancelledException; | 30 import org.tmatesoft.hg.util.CancelledException; |
| 28 import org.tmatesoft.hg.util.LogFacility.Severity; | 31 import org.tmatesoft.hg.util.LogFacility.Severity; |
| 29 import org.tmatesoft.hg.util.Path; | 32 import org.tmatesoft.hg.util.Path; |
| 30 | 33 |
| 35 */ | 38 */ |
| 36 public class WorkingDirFileWriter implements ByteChannel { | 39 public class WorkingDirFileWriter implements ByteChannel { |
| 37 | 40 |
| 38 | 41 |
| 39 private final Internals hgRepo; | 42 private final Internals hgRepo; |
| 43 private final boolean execCap, symlinkCap; | |
| 44 private final FileSystemHelper fileFlagsHelper; | |
| 40 private File dest; | 45 private File dest; |
| 41 private FileChannel destChannel; | 46 private FileChannel destChannel; |
| 42 private int totalBytesWritten; | 47 private int totalBytesWritten; |
| 48 private ByteArrayChannel linkChannel; | |
| 49 private int fmode; | |
| 43 | 50 |
| 44 public WorkingDirFileWriter(Internals internalRepo) { | 51 public WorkingDirFileWriter(Internals internalRepo) { |
| 45 hgRepo = internalRepo; | 52 hgRepo = internalRepo; |
| 53 execCap = Internals.checkSupportsExecutables(internalRepo.getRepo().getWorkingDir()); | |
| 54 symlinkCap = Internals.checkSupportsSymlinks(internalRepo.getRepo().getWorkingDir()); | |
| 55 if (symlinkCap || execCap) { | |
| 56 fileFlagsHelper = new FileSystemHelper(internalRepo.getSessionContext()); | |
| 57 } else { | |
| 58 fileFlagsHelper = null; | |
| 59 } | |
| 46 } | 60 } |
| 47 | 61 |
| 48 /** | 62 /** |
| 49 * Information purposes only, to find out trouble location if {@link #processFile(HgDataFile, int)} fails | 63 * Writes content of specified file revision into local filesystem, or create a symlink according to flags. |
| 64 * Executable bit is set if specified and filesystem supports it. | |
| 50 */ | 65 */ |
| 51 public File getDestinationFile() { | 66 public void processFile(HgDataFile df, int fileRevIndex, HgManifest.Flags flags) throws IOException { |
| 52 return dest; | |
| 53 } | |
| 54 | |
| 55 public void processFile(HgDataFile df, int fileRevIndex) throws IOException { | |
| 56 try { | 67 try { |
| 57 prepare(df.getPath()); | 68 prepare(df.getPath()); |
| 69 if (flags != HgManifest.Flags.Link) { | |
| 70 destChannel = new FileOutputStream(dest).getChannel(); | |
| 71 } else { | |
| 72 linkChannel = new ByteArrayChannel(); | |
| 73 } | |
| 58 df.contentWithFilters(fileRevIndex, this); | 74 df.contentWithFilters(fileRevIndex, this); |
| 59 } catch (CancelledException ex) { | 75 } catch (CancelledException ex) { |
| 60 hgRepo.getSessionContext().getLog().dump(getClass(), Severity.Error, ex, "Our impl doesn't throw cancellation"); | 76 hgRepo.getSessionContext().getLog().dump(getClass(), Severity.Error, ex, "Our impl doesn't throw cancellation"); |
| 77 } finally { | |
| 78 if (flags != HgManifest.Flags.Link) { | |
| 79 destChannel.close(); | |
| 80 destChannel = null; | |
| 81 // leave dest in case anyone enquires with #getDestinationFile | |
| 82 } | |
| 61 } | 83 } |
| 62 finish(); | 84 if (linkChannel != null && symlinkCap) { |
| 85 assert flags == HgManifest.Flags.Link; | |
| 86 fileFlagsHelper.createSymlink(dest.getParentFile(), dest.getName(), linkChannel.toArray()); | |
| 87 } else if (flags == HgManifest.Flags.Exec && execCap) { | |
| 88 fileFlagsHelper.setExecutableBit(dest.getParentFile(), dest.getName()); | |
| 89 } | |
| 90 // Although HgWCStatusCollector treats 644 (`hg manifest -v`) and 664 (my fs) the same, it's better | |
| 91 // to detect actual flags here | |
| 92 fmode = flags.fsMode(); // default to one from manifest | |
| 93 if (fileFlagsHelper != null) { | |
| 94 // if neither execBit nor link is supported by fs, it's unlikely file mode is supported, too. | |
| 95 try { | |
| 96 fmode = fileFlagsHelper.getFileMode(dest, fmode); | |
| 97 } catch (IOException ex) { | |
| 98 // Warn, we've got default value and can live with it | |
| 99 hgRepo.getSessionContext().getLog().dump(getClass(), Warn, ex, "Failed get file access rights"); | |
| 100 } | |
| 101 } | |
| 63 } | 102 } |
| 64 | 103 |
| 65 public void prepare(Path fname) throws IOException { | 104 public void prepare(Path fname) throws IOException { |
| 66 String fpath = fname.toString(); | 105 String fpath = fname.toString(); |
| 67 dest = new File(hgRepo.getRepo().getWorkingDir(), fpath); | 106 dest = new File(hgRepo.getRepo().getWorkingDir(), fpath); |
| 68 if (fpath.indexOf('/') != -1) { | 107 if (fpath.indexOf('/') != -1) { |
| 69 dest.getParentFile().mkdirs(); | 108 dest.getParentFile().mkdirs(); |
| 70 } | 109 } |
| 71 destChannel = new FileOutputStream(dest).getChannel(); | 110 destChannel = null; |
| 111 linkChannel = null; | |
| 72 totalBytesWritten = 0; | 112 totalBytesWritten = 0; |
| 113 fmode = 0; | |
| 73 } | 114 } |
| 74 | 115 |
| 75 public int write(ByteBuffer buffer) throws IOException, CancelledException { | 116 public int write(ByteBuffer buffer) throws IOException, CancelledException { |
| 76 int written = destChannel.write(buffer); | 117 final int written; |
| 118 if (linkChannel != null) { | |
| 119 written = linkChannel.write(buffer); | |
| 120 } else { | |
| 121 written = destChannel.write(buffer); | |
| 122 } | |
| 77 totalBytesWritten += written; | 123 totalBytesWritten += written; |
| 78 return written; | 124 return written; |
| 79 } | 125 } |
| 80 | 126 |
| 81 public void finish() throws IOException { | 127 /** |
| 82 destChannel.close(); | 128 * Information purposes only, to find out trouble location if {@link #processFile(HgDataFile, int)} fails |
| 83 dest = null; | 129 */ |
| 130 public File getDestinationFile() { | |
| 131 return dest; | |
| 84 } | 132 } |
| 85 | 133 |
| 86 public int bytesWritten() { | 134 public int bytesWritten() { |
| 87 return totalBytesWritten; | 135 return totalBytesWritten; |
| 88 } | 136 } |
| 137 | |
| 138 public int fmode() { | |
| 139 return fmode; | |
| 140 } | |
| 141 | |
| 142 public int mtime() { | |
| 143 return (int) (dest.lastModified() / 1000); | |
| 144 } | |
| 89 } | 145 } |
