annotate extra/fast-export/hg-fast-export.py @ 1561:6074fffd8a1d feature_1136

No, a bare repo is better
author Chris Cannam
date Thu, 14 Jan 2016 12:03:06 +0000
parents e9e55585ebf2
children 3ad53f43483d
rev   line source
chris@1544 1 #!/usr/bin/env python
chris@1544 2
chris@1544 3 # Copyright (c) 2007, 2008 Rocco Rutte <pdmef@gmx.net> and others.
chris@1544 4 # License: MIT <http://www.opensource.org/licenses/mit-license.php>
chris@1544 5
chris@1544 6 from mercurial import node
chris@1544 7 from hg2git import setup_repo,fixup_user,get_branch,get_changeset
chris@1544 8 from hg2git import load_cache,save_cache,get_git_sha1,set_default_branch,set_origin_name
chris@1544 9 from optparse import OptionParser
chris@1544 10 import re
chris@1544 11 import sys
chris@1544 12 import os
chris@1544 13
chris@1544 14 if sys.platform == "win32":
chris@1544 15 # On Windows, sys.stdout is initially opened in text mode, which means that
chris@1544 16 # when a LF (\n) character is written to sys.stdout, it will be converted
chris@1544 17 # into CRLF (\r\n). That makes git blow up, so use this platform-specific
chris@1544 18 # code to change the mode of sys.stdout to binary.
chris@1544 19 import msvcrt
chris@1544 20 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
chris@1544 21
chris@1544 22 # silly regex to catch Signed-off-by lines in log message
chris@1544 23 sob_re=re.compile('^Signed-[Oo]ff-[Bb]y: (.+)$')
chris@1544 24 # insert 'checkpoint' command after this many commits or none at all if 0
chris@1544 25 cfg_checkpoint_count=0
chris@1544 26 # write some progress message every this many file contents written
chris@1544 27 cfg_export_boundary=1000
chris@1544 28
chris@1544 29 def gitmode(flags):
chris@1544 30 return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644'
chris@1544 31
chris@1544 32 def wr_no_nl(msg=''):
chris@1544 33 if msg:
chris@1544 34 sys.stdout.write(msg)
chris@1544 35
chris@1544 36 def wr(msg=''):
chris@1544 37 wr_no_nl(msg)
chris@1544 38 sys.stdout.write('\n')
chris@1544 39 #map(lambda x: sys.stderr.write('\t[%s]\n' % x),msg.split('\n'))
chris@1544 40
chris@1544 41 def checkpoint(count):
chris@1544 42 count=count+1
chris@1544 43 if cfg_checkpoint_count>0 and count%cfg_checkpoint_count==0:
chris@1544 44 sys.stderr.write("Checkpoint after %d commits\n" % count)
chris@1544 45 wr('checkpoint')
chris@1544 46 wr()
chris@1544 47 return count
chris@1544 48
chris@1544 49 def revnum_to_revref(rev, old_marks):
chris@1544 50 """Convert an hg revnum to a git-fast-import rev reference (an SHA1
chris@1544 51 or a mark)"""
chris@1544 52 return old_marks.get(rev) or ':%d' % (rev+1)
chris@1544 53
chris@1544 54 def file_mismatch(f1,f2):
chris@1544 55 """See if two revisions of a file are not equal."""
chris@1544 56 return node.hex(f1)!=node.hex(f2)
chris@1544 57
chris@1544 58 def split_dict(dleft,dright,l=[],c=[],r=[],match=file_mismatch):
chris@1544 59 """Loop over our repository and find all changed and missing files."""
chris@1544 60 for left in dleft.keys():
chris@1544 61 right=dright.get(left,None)
chris@1544 62 if right==None:
chris@1544 63 # we have the file but our parent hasn't: add to left set
chris@1544 64 l.append(left)
chris@1544 65 elif match(dleft[left],right) or gitmode(dleft.flags(left))!=gitmode(dright.flags(left)):
chris@1544 66 # we have it but checksums mismatch: add to center set
chris@1544 67 c.append(left)
chris@1544 68 for right in dright.keys():
chris@1544 69 left=dleft.get(right,None)
chris@1544 70 if left==None:
chris@1544 71 # if parent has file but we don't: add to right set
chris@1544 72 r.append(right)
chris@1544 73 # change is already handled when comparing child against parent
chris@1544 74 return l,c,r
chris@1544 75
chris@1544 76 def get_filechanges(repo,revision,parents,mleft):
chris@1544 77 """Given some repository and revision, find all changed/deleted files."""
chris@1544 78 l,c,r=[],[],[]
chris@1544 79 for p in parents:
chris@1544 80 if p<0: continue
chris@1544 81 mright=repo.changectx(p).manifest()
chris@1544 82 l,c,r=split_dict(mleft,mright,l,c,r)
chris@1544 83 l.sort()
chris@1544 84 c.sort()
chris@1544 85 r.sort()
chris@1544 86 return l,c,r
chris@1544 87
chris@1544 88 def get_author(logmessage,committer,authors):
chris@1544 89 """As git distincts between author and committer of a patch, try to
chris@1544 90 extract author by detecting Signed-off-by lines.
chris@1544 91
chris@1544 92 This walks from the end of the log message towards the top skipping
chris@1544 93 empty lines. Upon the first non-empty line, it walks all Signed-off-by
chris@1544 94 lines upwards to find the first one. For that (if found), it extracts
chris@1544 95 authorship information the usual way (authors table, cleaning, etc.)
chris@1544 96
chris@1544 97 If no Signed-off-by line is found, this defaults to the committer.
chris@1544 98
chris@1544 99 This may sound stupid (and it somehow is), but in log messages we
chris@1544 100 accidentially may have lines in the middle starting with
chris@1544 101 "Signed-off-by: foo" and thus matching our detection regex. Prevent
chris@1544 102 that."""
chris@1544 103
chris@1544 104 loglines=logmessage.split('\n')
chris@1544 105 i=len(loglines)
chris@1544 106 # from tail walk to top skipping empty lines
chris@1544 107 while i>=0:
chris@1544 108 i-=1
chris@1544 109 if len(loglines[i].strip())==0: continue
chris@1544 110 break
chris@1544 111 if i>=0:
chris@1544 112 # walk further upwards to find first sob line, store in 'first'
chris@1544 113 first=None
chris@1544 114 while i>=0:
chris@1544 115 m=sob_re.match(loglines[i])
chris@1544 116 if m==None: break
chris@1544 117 first=m
chris@1544 118 i-=1
chris@1544 119 # if the last non-empty line matches our Signed-Off-by regex: extract username
chris@1544 120 if first!=None:
chris@1544 121 r=fixup_user(first.group(1),authors)
chris@1544 122 return r
chris@1544 123 return committer
chris@1544 124
chris@1544 125 def export_file_contents(ctx,manifest,files,hgtags,encoding=''):
chris@1544 126 count=0
chris@1544 127 max=len(files)
chris@1544 128 for file in files:
chris@1544 129 # Skip .hgtags files. They only get us in trouble.
chris@1544 130 if not hgtags and file == ".hgtags":
chris@1544 131 sys.stderr.write('Skip %s\n' % (file))
chris@1544 132 continue
chris@1544 133 d=ctx.filectx(file).data()
chris@1544 134 if encoding:
chris@1544 135 filename=file.decode(encoding).encode('utf8')
chris@1544 136 else:
chris@1544 137 filename=file
chris@1544 138 wr('M %s inline %s' % (gitmode(manifest.flags(file)),
chris@1544 139 strip_leading_slash(filename)))
chris@1544 140 wr('data %d' % len(d)) # had some trouble with size()
chris@1544 141 wr(d)
chris@1544 142 count+=1
chris@1544 143 if count%cfg_export_boundary==0:
chris@1544 144 sys.stderr.write('Exported %d/%d files\n' % (count,max))
chris@1544 145 if max>cfg_export_boundary:
chris@1544 146 sys.stderr.write('Exported %d/%d files\n' % (count,max))
chris@1544 147
chris@1544 148 def sanitize_name(name,what="branch"):
chris@1544 149 """Sanitize input roughly according to git-check-ref-format(1)"""
chris@1544 150
chris@1544 151 def dot(name):
chris@1544 152 if name[0] == '.': return '_'+name[1:]
chris@1544 153 return name
chris@1544 154
chris@1544 155 n=name
chris@1544 156 p=re.compile('([[ ~^:?\\\\*]|\.\.)')
chris@1544 157 n=p.sub('_', n)
chris@1544 158 if n[-1] in ('/', '.'): n=n[:-1]+'_'
chris@1544 159 n='/'.join(map(dot,n.split('/')))
chris@1544 160 p=re.compile('_+')
chris@1544 161 n=p.sub('_', n)
chris@1544 162
chris@1544 163 if n!=name:
chris@1544 164 sys.stderr.write('Warning: sanitized %s [%s] to [%s]\n' % (what,name,n))
chris@1544 165 return n
chris@1544 166
chris@1544 167 def strip_leading_slash(filename):
chris@1544 168 if filename[0] == '/':
chris@1544 169 return filename[1:]
chris@1544 170 return filename
chris@1544 171
chris@1544 172 def export_commit(ui,repo,revision,old_marks,max,count,authors,
chris@1544 173 branchesmap,sob,brmap,hgtags,notes,encoding='',fn_encoding=''):
chris@1544 174 def get_branchname(name):
chris@1544 175 if brmap.has_key(name):
chris@1544 176 return brmap[name]
chris@1544 177 n=sanitize_name(branchesmap.get(name,name))
chris@1544 178 brmap[name]=n
chris@1544 179 return n
chris@1544 180
chris@1544 181 (revnode,_,user,(time,timezone),files,desc,branch,_)=get_changeset(ui,repo,revision,authors,encoding)
chris@1544 182
chris@1544 183 branch=get_branchname(branch)
chris@1544 184
chris@1544 185 parents = [p for p in repo.changelog.parentrevs(revision) if p >= 0]
chris@1544 186
chris@1544 187 if len(parents)==0 and revision != 0:
chris@1544 188 wr('reset refs/heads/%s' % branch)
chris@1544 189
chris@1544 190 wr('commit refs/heads/%s' % branch)
chris@1544 191 wr('mark :%d' % (revision+1))
chris@1544 192 if sob:
chris@1544 193 wr('author %s %d %s' % (get_author(desc,user,authors),time,timezone))
chris@1544 194 wr('committer %s %d %s' % (user,time,timezone))
chris@1544 195 wr('data %d' % (len(desc)+1)) # wtf?
chris@1544 196 wr(desc)
chris@1544 197 wr()
chris@1544 198
chris@1544 199 ctx=repo.changectx(str(revision))
chris@1544 200 man=ctx.manifest()
chris@1544 201 added,changed,removed,type=[],[],[],''
chris@1544 202
chris@1544 203 if len(parents) == 0:
chris@1544 204 # first revision: feed in full manifest
chris@1544 205 added=man.keys()
chris@1544 206 added.sort()
chris@1544 207 type='full'
chris@1544 208 else:
chris@1544 209 wr('from %s' % revnum_to_revref(parents[0], old_marks))
chris@1544 210 if len(parents) == 1:
chris@1544 211 # later non-merge revision: feed in changed manifest
chris@1544 212 # if we have exactly one parent, just take the changes from the
chris@1544 213 # manifest without expensively comparing checksums
chris@1544 214 f=repo.status(repo.lookup(parents[0]),revnode)[:3]
chris@1544 215 added,changed,removed=f[1],f[0],f[2]
chris@1544 216 type='simple delta'
chris@1544 217 else: # a merge with two parents
chris@1544 218 wr('merge %s' % revnum_to_revref(parents[1], old_marks))
chris@1544 219 # later merge revision: feed in changed manifest
chris@1544 220 # for many files comparing checksums is expensive so only do it for
chris@1544 221 # merges where we really need it due to hg's revlog logic
chris@1544 222 added,changed,removed=get_filechanges(repo,revision,parents,man)
chris@1544 223 type='thorough delta'
chris@1544 224
chris@1544 225 sys.stderr.write('%s: Exporting %s revision %d/%d with %d/%d/%d added/changed/removed files\n' %
chris@1544 226 (branch,type,revision+1,max,len(added),len(changed),len(removed)))
chris@1544 227
chris@1544 228 if fn_encoding:
chris@1544 229 removed=[r.decode(fn_encoding).encode('utf8') for r in removed]
chris@1544 230
chris@1544 231 removed=[strip_leading_slash(x) for x in removed]
chris@1544 232
chris@1544 233 map(lambda r: wr('D %s' % r),removed)
chris@1544 234 export_file_contents(ctx,man,added,hgtags,fn_encoding)
chris@1544 235 export_file_contents(ctx,man,changed,hgtags,fn_encoding)
chris@1544 236 wr()
chris@1544 237
chris@1544 238 count=checkpoint(count)
chris@1544 239 count=generate_note(user,time,timezone,revision,ctx,count,notes)
chris@1544 240 return count
chris@1544 241
chris@1544 242 def generate_note(user,time,timezone,revision,ctx,count,notes):
chris@1544 243 if not notes:
chris@1544 244 return count
chris@1544 245 wr('commit refs/notes/hg')
chris@1544 246 wr('committer %s %d %s' % (user,time,timezone))
chris@1544 247 wr('data 0')
chris@1544 248 wr('N inline :%d' % (revision+1))
chris@1544 249 hg_hash=ctx.hex()
chris@1544 250 wr('data %d' % (len(hg_hash)))
chris@1544 251 wr_no_nl(hg_hash)
chris@1544 252 wr()
chris@1544 253 return checkpoint(count)
chris@1544 254
chris@1544 255 def export_tags(ui,repo,old_marks,mapping_cache,count,authors,tagsmap):
chris@1544 256 l=repo.tagslist()
chris@1544 257 for tag,node in l:
chris@1544 258 # Remap the branch name
chris@1544 259 tag=sanitize_name(tagsmap.get(tag,tag),"tag")
chris@1544 260 # ignore latest revision
chris@1544 261 if tag=='tip': continue
chris@1544 262 # ignore tags to nodes that are missing (ie, 'in the future')
chris@1544 263 if node.encode('hex_codec') not in mapping_cache:
chris@1544 264 sys.stderr.write('Tag %s refers to unseen node %s\n' % (tag, node.encode('hex_codec')))
chris@1544 265 continue
chris@1544 266
chris@1544 267 rev=int(mapping_cache[node.encode('hex_codec')])
chris@1544 268
chris@1544 269 ref=revnum_to_revref(rev, old_marks)
chris@1544 270 if ref==None:
chris@1544 271 sys.stderr.write('Failed to find reference for creating tag'
chris@1544 272 ' %s at r%d\n' % (tag,rev))
chris@1544 273 continue
chris@1544 274 sys.stderr.write('Exporting tag [%s] at [hg r%d] [git %s]\n' % (tag,rev,ref))
chris@1544 275 wr('reset refs/tags/%s' % tag)
chris@1544 276 wr('from %s' % ref)
chris@1544 277 wr()
chris@1544 278 count=checkpoint(count)
chris@1544 279 return count
chris@1544 280
chris@1544 281 def load_mapping(name, filename):
chris@1544 282 cache={}
chris@1544 283 if not os.path.exists(filename):
chris@1544 284 return cache
chris@1544 285 f=open(filename,'r')
chris@1544 286 l=0
chris@1544 287 a=0
chris@1544 288 lre=re.compile('^([^=]+)[ ]*=[ ]*(.+)$')
chris@1544 289 for line in f.readlines():
chris@1544 290 l+=1
chris@1544 291 line=line.strip()
chris@1544 292 if line=='' or line[0]=='#':
chris@1544 293 continue
chris@1544 294 m=lre.match(line)
chris@1544 295 if m==None:
chris@1544 296 sys.stderr.write('Invalid file format in [%s], line %d\n' % (filename,l))
chris@1544 297 continue
chris@1544 298 # put key:value in cache, key without ^:
chris@1544 299 cache[m.group(1).strip()]=m.group(2).strip()
chris@1544 300 a+=1
chris@1544 301 f.close()
chris@1544 302 sys.stderr.write('Loaded %d %s\n' % (a, name))
chris@1544 303 return cache
chris@1544 304
chris@1544 305 def branchtip(repo, heads):
chris@1544 306 '''return the tipmost branch head in heads'''
chris@1544 307 tip = heads[-1]
chris@1544 308 for h in reversed(heads):
chris@1544 309 if 'close' not in repo.changelog.read(h)[5]:
chris@1544 310 tip = h
chris@1544 311 break
chris@1544 312 return tip
chris@1544 313
chris@1544 314 def verify_heads(ui,repo,cache,force):
chris@1544 315 branches={}
chris@1544 316 for bn, heads in repo.branchmap().iteritems():
chris@1544 317 branches[bn] = branchtip(repo, heads)
chris@1544 318 l=[(-repo.changelog.rev(n), n, t) for t, n in branches.items()]
chris@1544 319 l.sort()
chris@1544 320
chris@1544 321 # get list of hg's branches to verify, don't take all git has
chris@1544 322 for _,_,b in l:
chris@1544 323 b=get_branch(b)
chris@1544 324 sha1=get_git_sha1(b)
chris@1544 325 c=cache.get(b)
chris@1544 326 if sha1!=c:
chris@1544 327 sys.stderr.write('Error: Branch [%s] modified outside hg-fast-export:'
chris@1544 328 '\n%s (repo) != %s (cache)\n' % (b,sha1,c))
chris@1544 329 if not force: return False
chris@1544 330
chris@1544 331 # verify that branch has exactly one head
chris@1544 332 t={}
chris@1544 333 for h in repo.heads():
chris@1544 334 (_,_,_,_,_,_,branch,_)=get_changeset(ui,repo,h)
chris@1544 335 if t.get(branch,False):
chris@1544 336 sys.stderr.write('Error: repository has at least one unnamed head: hg r%s\n' %
chris@1544 337 repo.changelog.rev(h))
chris@1544 338 if not force: return False
chris@1544 339 t[branch]=True
chris@1544 340
chris@1544 341 return True
chris@1544 342
chris@1544 343 def hg2git(repourl,m,marksfile,mappingfile,headsfile,tipfile,
chris@1544 344 authors={},branchesmap={},tagsmap={},
chris@1544 345 sob=False,force=False,hgtags=False,notes=False,encoding='',fn_encoding=''):
chris@1544 346 _max=int(m)
chris@1544 347
chris@1544 348 old_marks=load_cache(marksfile,lambda s: int(s)-1)
chris@1544 349 mapping_cache=load_cache(mappingfile)
chris@1544 350 heads_cache=load_cache(headsfile)
chris@1544 351 state_cache=load_cache(tipfile)
chris@1544 352
chris@1544 353 ui,repo=setup_repo(repourl)
chris@1544 354
chris@1544 355 if not verify_heads(ui,repo,heads_cache,force):
chris@1544 356 return 1
chris@1544 357
chris@1544 358 try:
chris@1544 359 tip=repo.changelog.count()
chris@1544 360 except AttributeError:
chris@1544 361 tip=len(repo)
chris@1544 362
chris@1544 363 min=int(state_cache.get('tip',0))
chris@1544 364 max=_max
chris@1544 365 if _max<0 or max>tip:
chris@1544 366 max=tip
chris@1544 367
chris@1544 368 for rev in range(0,max):
chris@1544 369 (revnode,_,_,_,_,_,_,_)=get_changeset(ui,repo,rev,authors)
chris@1544 370 mapping_cache[revnode.encode('hex_codec')] = str(rev)
chris@1544 371
chris@1544 372
chris@1544 373 c=0
chris@1544 374 brmap={}
chris@1544 375 for rev in range(min,max):
chris@1544 376 c=export_commit(ui,repo,rev,old_marks,max,c,authors,branchesmap,
chris@1544 377 sob,brmap,hgtags,notes,encoding,fn_encoding)
chris@1544 378
chris@1544 379 state_cache['tip']=max
chris@1544 380 state_cache['repo']=repourl
chris@1544 381 save_cache(tipfile,state_cache)
chris@1544 382 save_cache(mappingfile,mapping_cache)
chris@1544 383
chris@1544 384 c=export_tags(ui,repo,old_marks,mapping_cache,c,authors,tagsmap)
chris@1544 385
chris@1544 386 sys.stderr.write('Issued %d commands\n' % c)
chris@1544 387
chris@1544 388 return 0
chris@1544 389
chris@1544 390 if __name__=='__main__':
chris@1544 391 def bail(parser,opt):
chris@1544 392 sys.stderr.write('Error: No %s option given\n' % opt)
chris@1544 393 parser.print_help()
chris@1544 394 sys.exit(2)
chris@1544 395
chris@1544 396 parser=OptionParser()
chris@1544 397
chris@1544 398 parser.add_option("-m","--max",type="int",dest="max",
chris@1544 399 help="Maximum hg revision to import")
chris@1544 400 parser.add_option("--mapping",dest="mappingfile",
chris@1544 401 help="File to read last run's hg-to-git SHA1 mapping")
chris@1544 402 parser.add_option("--marks",dest="marksfile",
chris@1544 403 help="File to read git-fast-import's marks from")
chris@1544 404 parser.add_option("--heads",dest="headsfile",
chris@1544 405 help="File to read last run's git heads from")
chris@1544 406 parser.add_option("--status",dest="statusfile",
chris@1544 407 help="File to read status from")
chris@1544 408 parser.add_option("-r","--repo",dest="repourl",
chris@1544 409 help="URL of repo to import")
chris@1544 410 parser.add_option("-s",action="store_true",dest="sob",
chris@1544 411 default=False,help="Enable parsing Signed-off-by lines")
chris@1544 412 parser.add_option("--hgtags",action="store_true",dest="hgtags",
chris@1544 413 default=False,help="Enable exporting .hgtags files")
chris@1544 414 parser.add_option("-A","--authors",dest="authorfile",
chris@1544 415 help="Read authormap from AUTHORFILE")
chris@1544 416 parser.add_option("-B","--branches",dest="branchesfile",
chris@1544 417 help="Read branch map from BRANCHESFILE")
chris@1544 418 parser.add_option("-T","--tags",dest="tagsfile",
chris@1544 419 help="Read tags map from TAGSFILE")
chris@1544 420 parser.add_option("-f","--force",action="store_true",dest="force",
chris@1544 421 default=False,help="Ignore validation errors by force")
chris@1544 422 parser.add_option("-M","--default-branch",dest="default_branch",
chris@1544 423 help="Set the default branch")
chris@1544 424 parser.add_option("-o","--origin",dest="origin_name",
chris@1544 425 help="use <name> as namespace to track upstream")
chris@1544 426 parser.add_option("--hg-hash",action="store_true",dest="notes",
chris@1544 427 default=False,help="Annotate commits with the hg hash as git notes in the hg namespace")
chris@1544 428 parser.add_option("-e",dest="encoding",
chris@1544 429 help="Assume commit and author strings retrieved from Mercurial are encoded in <encoding>")
chris@1544 430 parser.add_option("--fe",dest="fn_encoding",
chris@1544 431 help="Assume file names from Mercurial are encoded in <filename_encoding>")
chris@1544 432
chris@1544 433 (options,args)=parser.parse_args()
chris@1544 434
chris@1544 435 m=-1
chris@1544 436 if options.max!=None: m=options.max
chris@1544 437
chris@1544 438 if options.marksfile==None: bail(parser,'--marks')
chris@1544 439 if options.mappingfile==None: bail(parser,'--mapping')
chris@1544 440 if options.headsfile==None: bail(parser,'--heads')
chris@1544 441 if options.statusfile==None: bail(parser,'--status')
chris@1544 442 if options.repourl==None: bail(parser,'--repo')
chris@1544 443
chris@1544 444 a={}
chris@1544 445 if options.authorfile!=None:
chris@1544 446 a=load_mapping('authors', options.authorfile)
chris@1544 447
chris@1544 448 b={}
chris@1544 449 if options.branchesfile!=None:
chris@1544 450 b=load_mapping('branches', options.branchesfile)
chris@1544 451
chris@1544 452 t={}
chris@1544 453 if options.tagsfile!=None:
chris@1544 454 t=load_mapping('tags', options.tagsfile)
chris@1544 455
chris@1544 456 if options.default_branch!=None:
chris@1544 457 set_default_branch(options.default_branch)
chris@1544 458
chris@1544 459 if options.origin_name!=None:
chris@1544 460 set_origin_name(options.origin_name)
chris@1544 461
chris@1544 462 encoding=''
chris@1544 463 if options.encoding!=None:
chris@1544 464 encoding=options.encoding
chris@1544 465
chris@1544 466 fn_encoding=encoding
chris@1544 467 if options.fn_encoding!=None:
chris@1544 468 fn_encoding=options.fn_encoding
chris@1544 469
chris@1544 470 sys.exit(hg2git(options.repourl,m,options.marksfile,options.mappingfile,
chris@1544 471 options.headsfile, options.statusfile,
chris@1544 472 authors=a,branchesmap=b,tagsmap=t,
chris@1544 473 sob=options.sob,force=options.force,hgtags=options.hgtags,
chris@1544 474 notes=options.notes,encoding=encoding,fn_encoding=fn_encoding))