comparison hgext/extdiff.py @ 32217:affd753ddaf1

extdiff: copy back files to the working directory if the size changed In theory, it should be enough to pay attention only to the modification time when detecting if a snapshotted working directory file changed. In practice, BeyondCompare preserves all file attributes when syncing files at the directory level. (If you open the file and sync individual hunks, then mtime does change, and everything was being copied back as desired.) I'm not sure how many other synchronization tools would trigger this issue, but it's annoyingly inconsistent (if a single file is diffed, it isn't snapshotted, so the same BeyondCompare file sync operation _is_ visible, because wdir() is updated in place. I filed a bug with them, and they stated it is on their wish list, but won't be fixed in the near term. This isn't a complete fix (there is still the case of the size not changing), but this seems like a trivial enough change to fix most of the problem. I suppose we could fool around with making files in the other snapshot readonly, and copy back if we see the readonly bit copied. That seems pretty hacky though, and only works if the external tool copies all attributes.
author Matt Harbison <matt_harbison@yahoo.com>
date Sat, 06 May 2017 23:00:57 -0400
parents 53865692a354
children 8a1ff5ed620e
comparison
equal deleted inserted replaced
32216:98bb992bef19 32217:affd753ddaf1
99 dirname = "root" 99 dirname = "root"
100 if node is not None: 100 if node is not None:
101 dirname = '%s.%s' % (dirname, short(node)) 101 dirname = '%s.%s' % (dirname, short(node))
102 base = os.path.join(tmproot, dirname) 102 base = os.path.join(tmproot, dirname)
103 os.mkdir(base) 103 os.mkdir(base)
104 fns_and_mtime = [] 104 fnsandstat = []
105 105
106 if node is not None: 106 if node is not None:
107 ui.note(_('making snapshot of %d files from rev %s\n') % 107 ui.note(_('making snapshot of %d files from rev %s\n') %
108 (len(files), short(node))) 108 (len(files), short(node)))
109 else: 109 else:
122 ui.note(' %s\n' % wfn) 122 ui.note(' %s\n' % wfn)
123 123
124 if node is None: 124 if node is None:
125 dest = os.path.join(base, wfn) 125 dest = os.path.join(base, wfn)
126 126
127 fns_and_mtime.append((dest, repo.wjoin(fn), 127 fnsandstat.append((dest, repo.wjoin(fn), os.lstat(dest)))
128 os.lstat(dest).st_mtime)) 128 return dirname, fnsandstat
129 return dirname, fns_and_mtime
130 129
131 def dodiff(ui, repo, cmdline, pats, opts): 130 def dodiff(ui, repo, cmdline, pats, opts):
132 '''Do the actual diff: 131 '''Do the actual diff:
133 132
134 - copy to a temp structure if diffing 2 internal revisions 133 - copy to a temp structure if diffing 2 internal revisions
197 rev1b = '@%d' % repo[node1b].rev() 196 rev1b = '@%d' % repo[node1b].rev()
198 else: 197 else:
199 dir1b = None 198 dir1b = None
200 rev1b = '' 199 rev1b = ''
201 200
202 fns_and_mtime = [] 201 fnsandstat = []
203 202
204 # If node2 in not the wc or there is >1 change, copy it 203 # If node2 in not the wc or there is >1 change, copy it
205 dir2root = '' 204 dir2root = ''
206 rev2 = '' 205 rev2 = ''
207 if node2: 206 if node2:
210 elif len(common) > 1: 209 elif len(common) > 1:
211 #we only actually need to get the files to copy back to 210 #we only actually need to get the files to copy back to
212 #the working dir in this case (because the other cases 211 #the working dir in this case (because the other cases
213 #are: diffing 2 revisions or single file -- in which case 212 #are: diffing 2 revisions or single file -- in which case
214 #the file is already directly passed to the diff tool). 213 #the file is already directly passed to the diff tool).
215 dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot, 214 dir2, fnsandstat = snapshot(ui, repo, modadd, None, tmproot,
216 subrepos) 215 subrepos)
217 else: 216 else:
218 # This lets the diff tool open the changed file directly 217 # This lets the diff tool open the changed file directly
219 dir2 = '' 218 dir2 = ''
220 dir2root = repo.root 219 dir2root = repo.root
221 220
247 label2 = cmdutil.makefilename(repo, template, node2) 246 label2 = cmdutil.makefilename(repo, template, node2)
248 dir1a = repo.vfs.reljoin(tmproot, label1a) 247 dir1a = repo.vfs.reljoin(tmproot, label1a)
249 dir2 = repo.vfs.reljoin(tmproot, label2) 248 dir2 = repo.vfs.reljoin(tmproot, label2)
250 dir1b = None 249 dir1b = None
251 label1b = None 250 label1b = None
252 fns_and_mtime = [] 251 fnsandstat = []
253 252
254 # Function to quote file/dir names in the argument string. 253 # Function to quote file/dir names in the argument string.
255 # When not operating in 3-way mode, an empty string is 254 # When not operating in 3-way mode, an empty string is
256 # returned for parent2 255 # returned for parent2
257 replace = {'parent': dir1a, 'parent1': dir1a, 'parent2': dir1b, 256 replace = {'parent': dir1a, 'parent1': dir1a, 'parent2': dir1b,
273 cmdline = re.sub(regex, quote, cmdline) 272 cmdline = re.sub(regex, quote, cmdline)
274 273
275 ui.debug('running %r in %s\n' % (cmdline, tmproot)) 274 ui.debug('running %r in %s\n' % (cmdline, tmproot))
276 ui.system(cmdline, cwd=tmproot, blockedtag='extdiff') 275 ui.system(cmdline, cwd=tmproot, blockedtag='extdiff')
277 276
278 for copy_fn, working_fn, mtime in fns_and_mtime: 277 for copy_fn, working_fn, st in fnsandstat:
279 if os.lstat(copy_fn).st_mtime != mtime: 278 cpstat = os.lstat(copy_fn)
279 # Some tools copy the file and attributes, so mtime may not detect
280 # all changes. A size check will detect more cases, but not all.
281 # The only certain way to detect every case is to diff all files,
282 # which could be expensive.
283 if cpstat.st_mtime != st.st_mtime or cpstat.st_size != st.st_size:
280 ui.debug('file changed while diffing. ' 284 ui.debug('file changed while diffing. '
281 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)) 285 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn))
282 util.copyfile(copy_fn, working_fn) 286 util.copyfile(copy_fn, working_fn)
283 287
284 return 1 288 return 1