##// END OF EJS Templates
Add support for relinking on Windows....
Siddharth Agarwal -
r10218:750b7a4f stable
parent child Browse files
Show More
@@ -34,6 +34,8 b' def relink(ui, repo, origin=None, **opts'
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'))
@@ -45,7 +47,7 b' def relink(ui, repo, origin=None, **opts'
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()
@@ -68,16 +70,16 b' def collect(src, ui):'
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'))
@@ -87,8 +89,9 b' def prune(candidates, dst, ui):'
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
@@ -102,7 +105,7 b' def do_relink(src, dst, files, ui):'
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
@@ -118,14 +121,17 b' def do_relink(src, dst, files, ui):'
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
@@ -105,6 +105,18 b' def pconvert(path):'
105 def localpath(path):
105 def localpath(path):
106 return path
106 return path
107
107
108 def samefile(fpath1, fpath2):
109 """Returns whether path1 and path2 refer to the same file. This is only
110 guaranteed to work for files, not directories."""
111 return os.path.samefile(fpath1, fpath2)
112
113 def samedevice(fpath1, fpath2):
114 """Returns whether fpath1 and fpath2 are on the same device. This is only
115 guaranteed to work for files, not directories."""
116 st1 = os.lstat(fpath1)
117 st2 = os.lstat(fpath2)
118 return st1.st_dev == st2.st_dev
119
108 if sys.platform == 'darwin':
120 if sys.platform == 'darwin':
109 def realpath(path):
121 def realpath(path):
110 '''
122 '''
@@ -37,7 +37,7 b' def os_link(src, dst):'
37 except NotImplementedError: # Another fake error win Win98
37 except NotImplementedError: # Another fake error win Win98
38 raise OSError(errno.EINVAL, 'Hardlinking not supported')
38 raise OSError(errno.EINVAL, 'Hardlinking not supported')
39
39
40 def nlinks(pathname):
40 def _getfileinfo(pathname):
41 """Return number of hardlinks for the given file."""
41 """Return number of hardlinks for the given file."""
42 try:
42 try:
43 fh = win32file.CreateFile(pathname,
43 fh = win32file.CreateFile(pathname,
@@ -45,10 +45,39 b' def nlinks(pathname):'
45 None, win32file.OPEN_EXISTING, 0, None)
45 None, win32file.OPEN_EXISTING, 0, None)
46 res = win32file.GetFileInformationByHandle(fh)
46 res = win32file.GetFileInformationByHandle(fh)
47 fh.Close()
47 fh.Close()
48 return res[7]
48 return res
49 except pywintypes.error:
49 except pywintypes.error:
50 return None
51
52 def nlinks(pathname):
53 """Return number of hardlinks for the given file."""
54 res = _getfileinfo(pathname)
55 if res is not None:
56 return res[7]
57 else:
50 return os.lstat(pathname).st_nlink
58 return os.lstat(pathname).st_nlink
51
59
60 def samefile(fpath1, fpath2):
61 """Returns whether fpath1 and fpath2 refer to the same file. This is only
62 guaranteed to work for files, not directories."""
63 res1 = _getfileinfo(fpath1)
64 res2 = _getfileinfo(fpath2)
65 if res1 is not None and res2 is not None:
66 # Index 4 is the volume serial number, and 8 and 9 contain the file ID
67 return res1[4] == res2[4] and res1[8] == res2[8] and res1[9] == res2[9]
68 else:
69 return False
70
71 def samedevice(fpath1, fpath2):
72 """Returns whether fpath1 and fpath2 are on the same device. This is only
73 guaranteed to work for files, not directories."""
74 res1 = _getfileinfo(fpath1)
75 res2 = _getfileinfo(fpath2)
76 if res1 is not None and res2 is not None:
77 return res1[4] == res2[4]
78 else:
79 return False
80
52 def testpid(pid):
81 def testpid(pid):
53 '''return True if pid is still running or unable to
82 '''return True if pid is still running or unable to
54 determine, False otherwise'''
83 determine, False otherwise'''
@@ -12,12 +12,13 b' 2 files updated, 0 files merged, 0 files'
12 created new head
12 created new head
13 % relink
13 % relink
14 relinking .hg/store
14 relinking .hg/store
15 collected 4 candidate storage files
15 collected 5 candidate storage files
16 not linkable: 00changelog.i
16 not linkable: 00changelog.i
17 not linkable: 00manifest.i
17 not linkable: 00manifest.i
18 not linkable: data/b.i
18 not linkable: data/b.i
19 pruned down to 1 probably relinkable files
19 pruned down to 2 probably relinkable files
20 relink: data/a.i 1/1 files (1e+02%)
20 relink: data/a.i 1/2 files ( 50%)
21 not linkable: data/dummy.i
21 relinked 1 files (136 bytes reclaimed)
22 relinked 1 files (136 bytes reclaimed)
22 % check hardlinks
23 % check hardlinks
23 repo/.hg/store/data/a.i == clone/.hg/store/data/a.i
24 repo/.hg/store/data/a.i == clone/.hg/store/data/a.i
General Comments 0
You need to be logged in to leave comments. Login now