diff --git a/README.md b/README.md index 31610a8..2346074 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ defined filter methods in the [dos2unix](./plugins/dos2unix) and [branch_name_in_commit](./plugins/branch_name_in_commit) plugins. ``` -commit_data = {'branch': branch, 'parents': parents, 'author': author, 'desc': desc} +commit_data = {'branch': branch, 'parents': parents, 'author': author, 'desc': desc, 'revision': revision, 'hg_hash': hg_hash} def commit_message_filter(self,commit_data): ``` @@ -201,6 +201,8 @@ exactly one head each. Otherwise commits to the tip of these heads within the branch will get flattened into merge commits. Chris J Billington's [hg-export-tool] can help you to handle branches with duplicate heads. +Alternatively, you can use the [head2branch plugin](./plugins/head2branch) +to create a new named branch from an unnamed head. hg-fast-export will ignore any files or directories tracked by mercurial called `.git`, and will print a warning if it encounters one. Git cannot diff --git a/hg-fast-export.py b/hg-fast-export.py index ed9fad0..4627eb1 100755 --- a/hg-fast-export.py +++ b/hg-fast-export.py @@ -302,9 +302,10 @@ def export_commit(ui,repo,revision,old_marks,max,count,authors, parents = [p for p in repo.changelog.parentrevs(revision) if p >= 0] author = get_author(desc,user,authors) + hg_hash=revsymbol(repo,b"%d" % revision).hex() if plugins and plugins['commit_message_filters']: - commit_data = {'branch': branch, 'parents': parents, 'author': author, 'desc': desc} + commit_data = {'branch': branch, 'parents': parents, 'author': author, 'desc': desc, 'revision': revision, 'hg_hash': hg_hash} for filter in plugins['commit_message_filters']: filter(commit_data) branch = commit_data['branch'] @@ -475,7 +476,7 @@ def branchtip(repo, heads): break return tip -def verify_heads(ui,repo,cache,force,branchesmap): +def verify_heads(ui,repo,cache,force,ignore_unnamed_heads,branchesmap): branches={} for bn, heads in repo.branchmap().iteritems(): branches[bn] = branchtip(repo, heads) @@ -497,21 +498,23 @@ def verify_heads(ui,repo,cache,force,branchesmap): # verify that branch has exactly one head t={} + unnamed_heads=False for h in repo.filtered(b'visible').heads(): (_,_,_,_,_,_,branch,_)=get_changeset(ui,repo,h) if t.get(branch,False): stderr_buffer.write( - b'Error: repository has at least one unnamed head: hg r%d\n' + b'Error: repository has an unnamed head: hg r%d\n' % repo.changelog.rev(h) ) - if not force: return False + unnamed_heads=True + if not force and not ignore_unnamed_heads: return False t[branch]=True - + if unnamed_heads and not force and not ignore_unnamed_heads: return False return True def hg2git(repourl,m,marksfile,mappingfile,headsfile,tipfile, authors={},branchesmap={},tagsmap={}, - sob=False,force=False,hgtags=False,notes=False,encoding='',fn_encoding='', + sob=False,force=False,ignore_unnamed_heads=False,hgtags=False,notes=False,encoding='',fn_encoding='', plugins={}): def check_cache(filename, contents): if len(contents) == 0: @@ -532,7 +535,7 @@ def hg2git(repourl,m,marksfile,mappingfile,headsfile,tipfile, ui,repo=setup_repo(repourl) - if not verify_heads(ui,repo,heads_cache,force,branchesmap): + if not verify_heads(ui,repo,heads_cache,force,ignore_unnamed_heads,branchesmap): return 1 try: @@ -618,7 +621,9 @@ if __name__=='__main__': parser.add_option("-T","--tags",dest="tagsfile", help="Read tags map from TAGSFILE") parser.add_option("-f","--force",action="store_true",dest="force", - default=False,help="Ignore validation errors by force") + default=False,help="Ignore validation errors by force, implies --ignore-unnamed-heads") + parser.add_option("--ignore-unnamed-heads",action="store_true",dest="ignore_unnamed_heads", + default=False,help="Ignore unnamed head errors") parser.add_option("-M","--default-branch",dest="default_branch", help="Set the default branch") parser.add_option("-o","--origin",dest="origin_name", @@ -714,6 +719,8 @@ if __name__=='__main__': sys.exit(hg2git(options.repourl,m,options.marksfile,options.mappingfile, options.headsfile, options.statusfile, authors=a,branchesmap=b,tagsmap=t, - sob=options.sob,force=options.force,hgtags=options.hgtags, + sob=options.sob,force=options.force, + ignore_unnamed_heads=options.ignore_unnamed_heads, + hgtags=options.hgtags, notes=options.notes,encoding=encoding,fn_encoding=fn_encoding, plugins=plugins_dict)) diff --git a/hg-fast-export.sh b/hg-fast-export.sh index 6533452..fd0ff53 100755 --- a/hg-fast-export.sh +++ b/hg-fast-export.sh @@ -45,7 +45,7 @@ if [ -z "${PYTHON}" ]; then exit 1 fi -USAGE="[--quiet] [-r ] [--force] [-m ] [-s] [--hgtags] [-A ] [-B ] [-T ] [-M ] [-o ] [--hg-hash] [-e ]" +USAGE="[--quiet] [-r ] [--force] [--ignore-unnamed-heads] [-m ] [-s] [--hgtags] [-A ] [-B ] [-T ] [-M ] [-o ] [--hg-hash] [-e ]" LONG_USAGE="Import hg repository up to either tip or If is omitted, use last hg repository as obtained from state file, GIT_DIR/$PFX-$SFX_STATE by default. diff --git a/plugins/head2branch/README.md b/plugins/head2branch/README.md new file mode 100644 index 0000000..932d98d --- /dev/null +++ b/plugins/head2branch/README.md @@ -0,0 +1,13 @@ +## Convert Head to Branch + +`fast-export` can only handle one head per branch. This plugin makes it possible +to create a new branch from a head by specifying the new branch name and +the first divergent commit for that head. + +Note: the hg hash must be in the full form, 40 hexadecimal characters. + +Note: you must run `fast-export` with `--ignore-unnamed-heads` option, +otherwise, the conversion will fail. + +To use the plugin, add the command line flag `--plugin head2branch=name,`. +The flag can be given multiple times to name more than one head. diff --git a/plugins/head2branch/__init__.py b/plugins/head2branch/__init__.py new file mode 100644 index 0000000..3ee9f40 --- /dev/null +++ b/plugins/head2branch/__init__.py @@ -0,0 +1,24 @@ +import sys + +def build_filter(args): + return Filter(args) + +class Filter: + + def __init__(self, args): + args = args.split(',') + self.branch_name = args[0].encode('ascii', 'replace') + self.starting_commit_hash = args[1].encode('ascii', 'strict') + self.branch_parents = set() + + def commit_message_filter(self, commit_data): + hg_hash = commit_data['hg_hash'] + rev = commit_data['revision'] + rev_parents = commit_data['parents'] + if (hg_hash == self.starting_commit_hash + or any(rp in self.branch_parents for rp in rev_parents) + ): + self.branch_parents.add(rev) + commit_data['branch'] = self.branch_name + sys.stderr.write('\nchanging r%s to branch %r\n' % (rev, self.branch_name)) + sys.stderr.flush()