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)