comparison hgext/graphlog.py @ 16173:9178d284b880

graphlog: implement --follow with file arguments
author Patrick Mezard <patrick@mezard.eu>
date Sat, 25 Feb 2012 22:11:36 +0100
parents 336e61875335
children 0a73c4bd9f47
comparison
equal deleted inserted replaced
16172:db75321c7a0e 16173:9178d284b880
239 def check_unsupported_flags(pats, opts): 239 def check_unsupported_flags(pats, opts):
240 for op in ["follow_first", "copies", "newest_first"]: 240 for op in ["follow_first", "copies", "newest_first"]:
241 if op in opts and opts[op]: 241 if op in opts and opts[op]:
242 raise util.Abort(_("-G/--graph option is incompatible with --%s") 242 raise util.Abort(_("-G/--graph option is incompatible with --%s")
243 % op.replace("_", "-")) 243 % op.replace("_", "-"))
244 if pats and opts.get('follow'):
245 raise util.Abort(_("-G/--graph option is incompatible with --follow "
246 "with file argument"))
247 244
248 def revset(repo, pats, opts): 245 def revset(repo, pats, opts):
249 """Return revset str built of revisions, log options and file patterns. 246 """Return revset str built of revisions, log options and file patterns.
250 """ 247 """
251 opt2revset = { 248 opt2revset = {
254 'only_merges': ('merge()', None), 251 'only_merges': ('merge()', None),
255 'removed': ('removes("*")', None), 252 'removed': ('removes("*")', None),
256 'date': ('date(%(val)r)', None), 253 'date': ('date(%(val)r)', None),
257 'branch': ('branch(%(val)r)', ' or '), 254 'branch': ('branch(%(val)r)', ' or '),
258 '_patslog': ('filelog(%(val)r)', ' or '), 255 '_patslog': ('filelog(%(val)r)', ' or '),
256 '_patsfollow': ('follow(%(val)r)', ' or '),
259 'keyword': ('keyword(%(val)r)', ' or '), 257 'keyword': ('keyword(%(val)r)', ' or '),
260 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '), 258 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
261 'user': ('user(%(val)r)', ' or '), 259 'user': ('user(%(val)r)', ' or '),
262 'rev': ('%(val)s', ' or '), 260 'rev': ('%(val)s', ' or '),
263 } 261 }
266 # branch and only_branch are really aliases and must be handled at 264 # branch and only_branch are really aliases and must be handled at
267 # the same time 265 # the same time
268 if 'branch' in opts and 'only_branch' in opts: 266 if 'branch' in opts and 'only_branch' in opts:
269 opts['branch'] = opts['branch'] + opts.pop('only_branch') 267 opts['branch'] = opts['branch'] + opts.pop('only_branch')
270 268
269 follow = opts.get('follow')
270 if 'follow' in opts:
271 del opts['follow']
271 # pats/include/exclude are passed to match.match() directly in 272 # pats/include/exclude are passed to match.match() directly in
272 # _matchfile() revset but walkchangerevs() builds its matcher with 273 # _matchfile() revset but walkchangerevs() builds its matcher with
273 # scmutil.match(). The difference is input pats are globbed on 274 # scmutil.match(). The difference is input pats are globbed on
274 # platforms without shell expansion (windows). 275 # platforms without shell expansion (windows).
275 match, pats = scmutil.matchandpats(repo[None], pats, opts) 276 pctx = repo[None]
277 match, pats = scmutil.matchandpats(pctx, pats, opts)
276 slowpath = match.anypats() or (match.files() and opts.get('removed')) 278 slowpath = match.anypats() or (match.files() and opts.get('removed'))
277 if not slowpath: 279 if not slowpath:
278 for f in match.files(): 280 for f in match.files():
281 if follow and f not in pctx:
282 raise util.Abort(_('cannot follow file not in parent '
283 'revision: "%s"') % f)
279 filelog = repo.file(f) 284 filelog = repo.file(f)
280 if not len(filelog): 285 if not len(filelog):
281 # A zero count may be a directory or deleted file, so 286 # A zero count may be a directory or deleted file, so
282 # try to find matching entries on the slow path. 287 # try to find matching entries on the slow path.
288 if follow:
289 raise util.Abort(
290 _('cannot follow nonexistent file: "%s"') % f)
283 slowpath = True 291 slowpath = True
284 if slowpath: 292 if slowpath:
285 # See cmdutil.walkchangerevs() slow path. 293 # See cmdutil.walkchangerevs() slow path.
286 # 294 #
295 if follow:
296 raise util.Abort(_('can only follow copies/renames for explicit '
297 'filenames'))
287 # pats/include/exclude cannot be represented as separate 298 # pats/include/exclude cannot be represented as separate
288 # revset expressions as their filtering logic applies at file 299 # revset expressions as their filtering logic applies at file
289 # level. For instance "-I a -X a" matches a revision touching 300 # level. For instance "-I a -X a" matches a revision touching
290 # "a" and "b" while "file(a) and not file(b)" does not. 301 # "a" and "b" while "file(a) and not file(b)" does not.
291 matchargs = [] 302 matchargs = []
296 for p in opts.get('exclude', []): 307 for p in opts.get('exclude', []):
297 matchargs.append('x:' + p) 308 matchargs.append('x:' + p)
298 matchargs = ','.join(('%r' % p) for p in matchargs) 309 matchargs = ','.join(('%r' % p) for p in matchargs)
299 opts['rev'] = opts.get('rev', []) + ['_matchfiles(%s)' % matchargs] 310 opts['rev'] = opts.get('rev', []) + ['_matchfiles(%s)' % matchargs]
300 else: 311 else:
301 opts['_patslog'] = list(pats) 312 if follow:
313 if pats:
314 opts['_patsfollow'] = list(pats)
315 else:
316 opts['follow'] = True
317 else:
318 opts['_patslog'] = list(pats)
302 319
303 revset = [] 320 revset = []
304 for op, val in opts.iteritems(): 321 for op, val in opts.iteritems():
305 if not val: 322 if not val:
306 continue 323 continue