Mercurial > hg
comparison hgext/relink.py @ 10218:750b7a4f01f6 stable
Add support for relinking on Windows.
Test and minor code change by Patrick Mézard <pmezard@gmail.com>
author | Siddharth Agarwal <sid.bugzilla@gmail.com> |
---|---|
date | Fri, 08 Jan 2010 18:48:39 +0530 |
parents | 819e6c7085fc |
children | 489b0caf21ed 6521605bc200 |
comparison
equal
deleted
inserted
replaced
10217:2bbb4c8eb27e | 10218:750b7a4f01f6 |
---|---|
32 then "default", in [paths]. | 32 then "default", in [paths]. |
33 | 33 |
34 Do not attempt any read operations on this repository while the command is | 34 Do not attempt any read operations on this repository while the command is |
35 running. (Both repositories will be locked against writes.) | 35 running. (Both repositories will be locked against writes.) |
36 """ | 36 """ |
37 if not hasattr(util, 'samefile') or not hasattr(util, 'samedevice'): | |
38 raise util.Abort(_('hardlinks are not supported on this system')) | |
37 src = hg.repository( | 39 src = hg.repository( |
38 cmdutil.remoteui(repo, opts), | 40 cmdutil.remoteui(repo, opts), |
39 ui.expandpath(origin or 'default-relink', origin or 'default')) | 41 ui.expandpath(origin or 'default-relink', origin or 'default')) |
40 if not src.local(): | 42 if not src.local(): |
41 raise util.Abort('must specify local origin repository') | 43 raise util.Abort('must specify local origin repository') |
43 locallock = repo.lock() | 45 locallock = repo.lock() |
44 try: | 46 try: |
45 remotelock = src.lock() | 47 remotelock = src.lock() |
46 try: | 48 try: |
47 candidates = collect(src.store.path, ui) | 49 candidates = collect(src.store.path, ui) |
48 targets = prune(candidates, repo.store.path, ui) | 50 targets = prune(candidates, src.store.path, repo.store.path, ui) |
49 do_relink(src.store.path, repo.store.path, targets, ui) | 51 do_relink(src.store.path, repo.store.path, targets, ui) |
50 finally: | 52 finally: |
51 remotelock.release() | 53 remotelock.release() |
52 finally: | 54 finally: |
53 locallock.release() | 55 locallock.release() |
66 candidates.append((os.path.join(relpath, filename), st)) | 68 candidates.append((os.path.join(relpath, filename), st)) |
67 | 69 |
68 ui.status(_('collected %d candidate storage files\n') % len(candidates)) | 70 ui.status(_('collected %d candidate storage files\n') % len(candidates)) |
69 return candidates | 71 return candidates |
70 | 72 |
71 def prune(candidates, dst, ui): | 73 def prune(candidates, src, dst, ui): |
72 def linkfilter(dst, st): | 74 def linkfilter(src, dst, st): |
73 try: | 75 try: |
74 ts = os.stat(dst) | 76 ts = os.stat(dst) |
75 except OSError: | 77 except OSError: |
76 # Destination doesn't have this file? | 78 # Destination doesn't have this file? |
77 return False | 79 return False |
78 if st.st_ino == ts.st_ino: | 80 if util.samefile(src, dst): |
79 return False | 81 return False |
80 if st.st_dev != ts.st_dev: | 82 if not util.samedevice(src, dst): |
81 # No point in continuing | 83 # No point in continuing |
82 raise util.Abort( | 84 raise util.Abort( |
83 _('source and destination are on different devices')) | 85 _('source and destination are on different devices')) |
84 if st.st_size != ts.st_size: | 86 if st.st_size != ts.st_size: |
85 return False | 87 return False |
86 return st | 88 return st |
87 | 89 |
88 targets = [] | 90 targets = [] |
89 for fn, st in candidates: | 91 for fn, st in candidates: |
92 srcpath = os.path.join(src, fn) | |
90 tgt = os.path.join(dst, fn) | 93 tgt = os.path.join(dst, fn) |
91 ts = linkfilter(tgt, st) | 94 ts = linkfilter(srcpath, tgt, st) |
92 if not ts: | 95 if not ts: |
93 ui.debug(_('not linkable: %s\n') % fn) | 96 ui.debug(_('not linkable: %s\n') % fn) |
94 continue | 97 continue |
95 targets.append((fn, ts.st_size)) | 98 targets.append((fn, ts.st_size)) |
96 | 99 |
100 def do_relink(src, dst, files, ui): | 103 def do_relink(src, dst, files, ui): |
101 def relinkfile(src, dst): | 104 def relinkfile(src, dst): |
102 bak = dst + '.bak' | 105 bak = dst + '.bak' |
103 os.rename(dst, bak) | 106 os.rename(dst, bak) |
104 try: | 107 try: |
105 os.link(src, dst) | 108 util.os_link(src, dst) |
106 except OSError: | 109 except OSError: |
107 os.rename(bak, dst) | 110 os.rename(bak, dst) |
108 raise | 111 raise |
109 os.remove(bak) | 112 os.remove(bak) |
110 | 113 |
116 total = len(files) | 119 total = len(files) |
117 for f, sz in files: | 120 for f, sz in files: |
118 pos += 1 | 121 pos += 1 |
119 source = os.path.join(src, f) | 122 source = os.path.join(src, f) |
120 tgt = os.path.join(dst, f) | 123 tgt = os.path.join(dst, f) |
121 sfp = file(source) | 124 # Binary mode, so that read() works correctly, especially on Windows |
122 dfp = file(tgt) | 125 sfp = file(source, 'rb') |
126 dfp = file(tgt, 'rb') | |
123 sin = sfp.read(CHUNKLEN) | 127 sin = sfp.read(CHUNKLEN) |
124 while sin: | 128 while sin: |
125 din = dfp.read(CHUNKLEN) | 129 din = dfp.read(CHUNKLEN) |
126 if sin != din: | 130 if sin != din: |
127 break | 131 break |
128 sin = sfp.read(CHUNKLEN) | 132 sin = sfp.read(CHUNKLEN) |
133 sfp.close() | |
134 dfp.close() | |
129 if sin: | 135 if sin: |
130 ui.debug(_('not linkable: %s\n') % f) | 136 ui.debug(_('not linkable: %s\n') % f) |
131 continue | 137 continue |
132 try: | 138 try: |
133 relinkfile(source, tgt) | 139 relinkfile(source, tgt) |