53 Commits

Author SHA1 Message Date
Frej Drejhammar
595587b245 Merge branch 'PR/197'
Closes #197, #185, #196
2020-02-09 19:39:21 +01:00
Matthijs van der Burgh
0b6b83c3de Adapt to status becoming an object in Mercurial 5.3
Status has always been a tuple, but since 5.3, commit:
https://www.mercurial-scm.org/repo/hg/rev/c5548b0b6847, it is an object.
Therefore the __getitem__ of the tuple isn't available anymore.

This fix is compatible with mercurial>=4.6, as the old status tuple
still has the same properties.
2020-02-08 17:23:30 +01:00
Frej Drejhammar
29a457eccf Merge branch 'PR/198'
Closes 198
2020-02-08 16:08:56 +01:00
Frej Drejhammar
4bc6dec5eb Merge branch 'PR/199'
Closes #199
2020-02-08 16:05:01 +01:00
Frej Drejhammar
fa8ebd994d Add link to what's expected for commit messages to the README 2020-02-08 15:50:17 +01:00
Frej Drejhammar
e83501d30d Make README issue tracker link a Markdown link 2020-02-08 15:43:10 +01:00
chrisjbillington
8efbb57822 Add additional options to branch_name_in_commit plugin
- Allow skipping writing the branch name if the branch is 'master'.

- Allow writing the branch name on the same line as the first line of
  the commit message separated by a colon, instead of it having its own
  line.
2020-02-07 20:48:49 -05:00
chrisjbillington
8d135fe700 Ignore files and directories called .git
Git cannot track these files. Print a warning if encountering one.

Fixes #166
2020-02-07 17:52:57 -05:00
Frej Drejhammar
ed36227c62 Merge branch 'PR/192'
Closes #192
2020-01-31 17:12:30 +01:00
Frej Drejhammar
507c17cc1b Revert "Handle --force option correctly in any position"
This reverts commit 0c5617bf8d.

The changes turned out to require bash. Traditionally we have tried to
stay compatible with plain old sh, so this is a revert.

Closes #195.
2020-01-31 17:01:04 +01:00
James Douglass
1841ba4be9 Add a plugin to prefix an issue number with a user-defined string. 2020-01-29 14:18:17 -08:00
Frej Drejhammar
30e54cb55c Merge branch 'PR/194'
Closes #194.
2020-01-29 19:20:48 +01:00
Frej Drejhammar
5f7bf7ee71 Merge branch 'PR/193'
Closes #193
2020-01-29 19:15:18 +01:00
Alexander Regueiro
0c5617bf8d Handle --force option correctly in any position 2020-01-29 18:13:54 +00:00
Frej Drejhammar
29ec91970e Merge branch 'PR/189-fixup-subrepo-list-refreshing-v2'
Fixes #187.
2020-01-28 19:43:49 +01:00
James Douglass
601daf60f7 Adding a new plugin to overwrite null messages. 2020-01-26 17:25:07 -08:00
MokhamedDakhraui
9c9669d361 Check .hgsub and .hgsubstate files to detect subrepo changes 2020-01-26 00:36:34 +03:00
Frej Drejhammar
2ba5d77435 Merge PR#183
Closes #183
2019-12-22 19:08:08 +01:00
Justin Murray
e8a681121b Document default branch behavior
Document the default behavior of renaming the `default` hg branch to `master`
on git, and how to override from the command line when this causes problems.

See also: #182
2019-12-21 15:34:30 -05:00
Frej Drejhammar
ffdd27c2da Merge branch 'mossop/PR/git-submodules-v3'
Closes #180
2019-12-07 19:36:31 +01:00
Dave Townsend
ab31fdcbaa Add support for git submodules
Mercurial supports not only submodules which are Mercurial
repositories, but also Git and Subversion repositories. This
patch adds support for submodules which are Git repositories to
hg-fast-export.

As submodules which are Git repositories won't need a mapping
file we trigger the submodule update only on the occurence of the
`.hgsubstate` file and push the check for a valid
`submodule_mappings` to `refresh_gitmodules(ctx)`
2019-12-07 10:22:23 -08:00
Dave Townsend
acf93a80a9 Only export submodules that exist in the submodule mapping. 2019-12-07 10:21:26 -08:00
Dave Townsend
0f49bfe0db Move hg sub-module updating to its own function, NFC
This refactoring is in preparation to supporting Mercurial
submodules which are git repositories.
2019-12-07 09:39:43 -08:00
Frej Drejhammar
3af916d664 Clarify requirements
Make it clear that python 2.7.x is a hard requirement and that
Mercurial >= 4.6 is required. Also clean up an old editing artefact.
2019-11-12 17:46:08 +01:00
Frej Drejhammar
02c54a5513 Merge branch 'Mossop-obsolete'
Closes #175
2019-10-20 19:54:08 +02:00
Dave Townsend
b54046d3aa Avoid showing a warning when the mercurial repository has obsolete markers. 2019-10-20 19:49:25 +02:00
Dave Townsend
ff1c885305 Ignore obsolete changesets in the source repository
Obsolete changesets are, for example, create by the Evolve
extension. This patch switches to an unfiltered repository (the
filtered one throws on an attempt to access obsolete revisions) and
then filters out the obsolete revisions when it comes across them.

Fixes #173
2019-10-20 19:45:42 +02:00
Frej Drejhammar
0096085b6f Tag maps should use the same syntax as branch and author maps
When version v171002 introduced a new mapping file format for branches
and authors, that change never made it to the remapping of tags
although the README documents it.

Fixes #172.
2019-10-12 21:09:14 +02:00
Frej Drejhammar
6f9bc6517a Merge branch 'pr/FAQ' 2019-09-24 22:56:42 +02:00
Frej Drejhammar
243100eea4 Add a section on frequent problems to the README
This tries to preemptively avoid recurrence of issues #148, #152,
 #155, #165 and #168.
2019-09-19 16:41:04 +02:00
Frej Drejhammar
1181a0af47 Allow name sanitizer to be disabled with --no-auto-sanitize
Make it possible to completely disable the name sanitizer by the
--no-auto-sanitize flag. Previously the sanitizer was run on user
remapped names. As the sanitizer rewrites perfectly legal git
names (such as __.*) this is probably not what the user wants.

Closes #155.
2019-09-13 14:56:32 +02:00
Frej Drejhammar
7ab47e002f Merge branch 'jpaugh-patch-1'
Closes #164
2019-09-12 20:14:42 +02:00
Jonathan Paugh
96762f5474 README: Fix broken links
Use "footnote" style links to prevent future issues whenever the text is formatted to a specific length.
2019-09-11 16:46:55 -05:00
Frej Drejhammar
fcdc91634a Merge branch 'be-non-pep349-tolerant'
Closes: #143
Closes: #160
2019-09-01 18:31:46 +02:00
Frej Drejhammar
f57fba000b Try to do the right thing on non PEP394 compliant systems
PEP 394 [1] tells us that on systems with both a python 2 and 3
installed, the python 2 interpreter should be installed as python2.

Unfortunately not all distributions adheres to PEP 394 (I'm looking at
you, Windows) so to handle that we first try to find a 'python2', then
fall back on plain 'python'. In order to not silently pick a python 3
by mistake, we check sys.version_info using the the interpreter we
found.

[1] https://www.python.org/dev/peps/pep-0394/
2019-09-01 18:31:18 +02:00
Frej Drejhammar
b25cbd6753 Merge branch 'pr/157-v3'
Closes #156
2019-08-18 11:57:53 +02:00
MokhamedDakhraui
581b1b3d17 Remove git submodules if .hgsubstate file was removed or emptied 2019-08-18 05:46:46 +03:00
MokhamedDakhraui
7df01ac323 Refactor refresh_gitmodules()
Use the change context substate field instead of manually parsing the `.hgsubstate` file.
2019-08-16 02:42:03 +03:00
MokhamedDakhraui
914f5a0dbe Replaced several lambdas by one loop 2019-08-16 02:41:54 +03:00
MokhamedDakhraui
8779cb5e95 Extract operations with submodules to separated methods 2019-08-16 02:40:44 +03:00
Johannes Carlsson
47d330de83 Add support for mercurial subrepos
This adds a new command line option (--subrepo-map) that will
map mercurial subrepos to git submodules.

The --subrepo-map takes a mapping file as an argument that will
be used to map a subrepo folder to a git submodule.

For more information see the README-SUBMODULES.md.

This commit is inspired by the changes made by daolis in PR#38
that was never merged.

Closes: #51
Closes: #147
2019-01-07 18:41:19 +01:00
Frej Drejhammar
b51c58d3e0 Merge branch 'thetradedesk-master'
Closes #144
2018-12-06 21:51:32 +01:00
Johan Henkens
cadcfcbe90 Move filter_contents to plugin system 2018-12-05 13:25:48 -08:00
Johan Henkens
5e7895ca6b Add branch_name_in_commit plugin 2018-12-05 13:25:48 -08:00
Johan Henkens
679103795b Add dos2unix plugin 2018-12-05 13:25:48 -08:00
Johan Henkens
e895ce087f Add plugin system 2018-12-05 13:25:47 -08:00
Johan Henkens
850094c498 Add gitattributes, additional ignores 2018-12-05 13:25:47 -08:00
Daniel Small
2bb173ef68 hg 4.7: Replace call to util.email with templatefilters.email
This change is required for Mercurial 4.7 support and fixes #137.
2018-08-11 15:49:08 +02:00
Frej Drejhammar
ac60034ba3 Adhere to PEP 394
From PEP 394 [1]:

* python2 will refer to some version of Python 2.x.

* end users should be aware that python refers to python3 on at least
  Arch Linux (that change is what prompted the creation of this PEP),
  so python should be used in the shebang line only for scripts that
  are source compatible with both Python 2 and 3.

So to make sure that we run correctly on a system where python refers
to python3 and avoid problems like issue #11 we change the shebangs.

[1] https://www.python.org/dev/peps/pep-0394/
2018-08-11 15:07:19 +02:00
Frej Drejhammar
eca99b61eb Merge branch 'atykhyy-as-binary'
This closes #95
2018-06-22 16:46:10 +02:00
Anton Tykhyy
89db1d93cf Add --filter-contents 2018-06-17 21:09:59 +03:00
Frej Drejhammar
e200cec39f Adapt to changes in Mercurial 4.6
Starting with Mercurial 4.6 repo.lookup() no longer accepts raw hashes
for lookups.
2018-06-10 15:51:09 +02:00
Gabriel
51d5f893db Add a section about system requirements to the README
Add @rinu's suggestion on how to run fast-export on Windows to the
README, this fixes #121.
2018-06-10 15:44:46 +02:00
18 changed files with 683 additions and 34 deletions

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto

2
.gitignore vendored
View File

@@ -1,2 +1,4 @@
*.orig
*.pyc *.pyc
.dotest .dotest
.idea/

75
README-SUBMODULES.md Normal file
View File

@@ -0,0 +1,75 @@
# How to convert Mercurial Repositories with subrepos
## Introduction
hg-fast-export supports migrating mercurial subrepositories in the
repository being converted into git submodules in the converted repository.
Git submodules must be git repositories while mercurial's subrepositories can
be git, mercurial or subversion repositories. hg-fast-export will handle any
git subrepositories automatically, any other kinds must first be converted
to git repositories. Currently hg-fast-export does not support the conversion
of subversion subrepositories. The rest of this page covers the conversion of
mercurial subrepositories which require some manual steps:
The first step for mercurial subrepositories involves converting the
subrepository into a git repository using hg-fast-export. When all
subrepositories have been converted, a mapping file that maps the mercurial
subrepository path to a converted git submodule path must be created. The
format for this file is:
"<mercurial subrepo path>"="<git submodule path>"
"<mercurial subrepo path2>"="<git submodule path2>"
...
The path of this mapping file is then provided with the --subrepo-map
command line option.
## Example
Example mercurial repo folder structure (~/mercurial):
src/...
subrepo/subrepo1
subrepo/subrepo2
### Setup
Create an empty new folder where all the converted git modules will be imported:
mkdir ~/imported-gits
cd ~/imported-gits
### Convert all submodules to git:
mkdir submodule1
cd submodule1
git init
hg-fast-export.sh -r ~/mercurial/subrepo1
cd ..
mkdir submodule2
cd submodule2
git init
hg-fast-export.sh -r ~/mercurial/subrepo2
### Create mapping file
cd ~/imported-gits
cat > submodule-mappings << EOF
"subrepo/subrepo1"="../submodule1"
"subrepo/subrepo2"="../submodule2"
EOF
### Convert main repository
cd ~/imported-gits
mkdir git-main-repo
cd git-main-repo
git init
hg-fast-export.sh -r ~/mercurial --subrepo-map=../submodule-mappings
### Result
The resulting repository will now contain the subrepo/subrepo1 and
subrepo/subrepo1 submodules. The created .gitmodules file will look
like:
[submodule "subrepo/subrepo1"]
path = subrepo/subrepo1
url = ../submodule1
[submodule "subrepo/subrepo2"]
path = subrepo/subrepo2
url = ../submodule2

167
README.md
View File

@@ -4,26 +4,38 @@ hg-fast-export.(sh|py) - mercurial to git converter using git-fast-import
Legal Legal
----- -----
Most hg-* scripts are licensed under the [MIT license] Most hg-* scripts are licensed under the [MIT license] and were written
(http://www.opensource.org/licenses/mit-license.php) and were written
by Rocco Rutte <pdmef@gmx.net> with hints and help from the git list and by Rocco Rutte <pdmef@gmx.net> with hints and help from the git list and
\#mercurial on freenode. hg-reset.py is licensed under GPLv2 since it \#mercurial on freenode. hg-reset.py is licensed under GPLv2 since it
copies some code from the mercurial sources. copies some code from the mercurial sources.
The current maintainer is Frej Drejhammar <frej.drejhammar@gmail.com>. The current maintainer is Frej Drejhammar <frej.drejhammar@gmail.com>.
[MIT license]: http://www.opensource.org/licenses/mit-license.php
Support Support
------- -------
If you have problems with hg-fast-export or have found a bug, please If you have problems with hg-fast-export or have found a bug, please
create an issue at the [github issue tracker] create an issue at the [github issue tracker]. Before creating a new
(https://github.com/frej/fast-export/issues). Before creating a new
issue, check that your problem has not already been addressed in an issue, check that your problem has not already been addressed in an
already closed issue. Do not contact the maintainer directly unless already closed issue. Do not contact the maintainer directly unless
you want to report a security bug. That way the next person having the you want to report a security bug. That way the next person having the
same problem can benefit from the time spent solving the problem the same problem can benefit from the time spent solving the problem the
first time. first time.
[github issue tracker]: https://github.com/frej/fast-export/issues
System Requirements
-------------------
This project depends on Python 2.7 and the Mercurial >= 4.6
package. If Python is not installed, install it before proceeding. The
Mercurial package can be installed with `pip install mercurial`.
On windows the bash that comes with "Git for Windows" is known to work
well.
Usage Usage
----- -----
@@ -89,6 +101,94 @@ name the -B and -T options allow a mapping file to be specified to
rename branches and tags (respectively). The syntax of the mapping rename branches and tags (respectively). The syntax of the mapping
file is the same as for the author mapping. file is the same as for the author mapping.
When the -B and -T flags are used, you will probably want to use the
-n flag to disable the built-in (broken in many cases) sanitizing of
branch/tag names. In the future -n will become the default, but in
order to not break existing incremental conversions, the default
remains with the old behavior.
By default, the `default` mercurial branch is renamed to the `master`
branch on git. If your mercurial repo contains both `default` and
`master` branches, you'll need to override this behavior. Use
`-M <newName>` to specify what name to give the `default` branch.
Content filtering
-----------------
hg-fast-export supports filtering the content of exported files.
The filter is supplied to the --filter-contents option. hg-fast-export
runs the filter for each exported file, pipes its content to the filter's
standard input, and uses the filter's standard output in place
of the file's original content. The prototypical use of this feature
is to convert line endings in text files from CRLF to git's preferred LF:
```
-- Start of crlf-filter.sh --
#!/bin/sh
# $1 = pathname of exported file relative to the root of the repo
# $2 = Mercurial's hash of the file
# $3 = "1" if Mercurial reports the file as binary, otherwise "0"
if [ "$3" == "1" ]; then cat; else dos2unix; fi
-- End of crlf-filter.sh --
```
Plugins
-----------------
hg-fast-export supports plugins to manipulate the file data and commit
metadata. The plugins are enabled with the --plugin option. The value
of said option is a plugin name (by folder in the plugins directory),
and optionally, and equals-sign followed by an initialization string.
There is a readme accompanying each of the bundled plugins, with a
description of the usage. To create a new plugin, one must simply
add a new folder under the `plugins` directory, with the name of the
new plugin. Inside, there must be an `__init__.py` file, which contains
at a minimum:
```
def build_filter(args):
return Filter(args)
class Filter:
def __init__(self, args):
pass
#Or don't pass, if you want to do some init code here
```
Beyond the boilerplate initialization, you can see the two different
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}
def commit_message_filter(self,commit_data):
```
The `commit_message_filter` method is called for each commit, after parsing
from hg, but before outputting to git. The dictionary `commit_data` contains the
above attributes about the commit, and can be modified by any filter. The
values in the dictionary after filters have been run are used to create the git
commit.
```
file_data = {'filename':filename,'file_ctx':file_ctx,'d':d}
def file_data_filter(self,file_data):
```
The `file_data_filter` method is called for each file within each commit.
The dictionary `file_data` contains the above attributes about the file, and
can be modified by any filter. `file_ctx` is the filecontext from the
mercurial python library. After all filters have been run, the values
are used to add the file to the git commit.
Submodules
----------
See README-SUBMODULES.md for how to convert subrepositories into git
submodules.
Notes/Limitations Notes/Limitations
----------------- -----------------
@@ -96,6 +196,11 @@ hg-fast-export supports multiple branches but only named branches with
exactly one head each. Otherwise commits to the tip of these heads exactly one head each. Otherwise commits to the tip of these heads
within the branch will get flattened into merge commits. within the branch will get flattened into merge commits.
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
track such files or directories. This is not to be confused with submodules,
which are described in README-SUBMODULES.md.
As each git-fast-import run creates a new pack file, it may be As each git-fast-import run creates a new pack file, it may be
required to repack the repository quite often for incremental imports required to repack the repository quite often for incremental imports
(especially when importing a small number of changesets per (especially when importing a small number of changesets per
@@ -118,6 +223,54 @@ saw never get modified.
Submitting Patches Submitting Patches
------------------ ------------------
Please use the issue-tracker at github Please use the [issue-tracker](https://github.com/frej/fast-export) at
https://github.com/frej/fast-export to report bugs and submit github to report bugs and submit patches.
patches.
Please read
[https://chris.beams.io/posts/git-commit/](https://chris.beams.io/posts/git-commit/)
on how to write a good commit message before submitting a pull request
for review. Although the article recommends at most 50 characters for
the subject, up to 72 characters are frequently accepted for
fast-export.
Frequent Problems
=================
* git fast-import crashes with: `error: cannot lock ref 'refs/heads/...`
Branch names in git behave as file names (as they are just files and
sub-directories under `refs/heads/`, and a path cannot name both a
file and a directory, i.e. the branches `a` and `a/b` can never
exist at the same time in a git repo.
Use a mapping file to rename the troublesome branch names.
* `Branch [<branch-name>] modified outside hg-fast-export` but I have
not touched the repo!
If you are running fast-export on a case-preserving but
case-insensitive file system (Windows and OSX), this will make git
treat `A` and `a` as the same branch. The solution is to use a
mapping file to rename branches which only differ in case.
* My mapping file does not seem to work when I rename the branch `git
fast-import` crashes on!
fast-export (imperfectly) mangles branch names it thinks won't be
valid. The mechanism cannot be removed as it would break already
existing incremental imports that expects it. When fast export
mangles a name, it prints out a warning of the form `Warning:
sanitized branch [<unmangled>] to [<mangled>]`. If `git fast-import`
crashes on `<mangled>`, you need to put `<unmangled>` into the
mapping file.
* fast-import mangles valid git branch names which I have remapped!
Use the `-n` flag to hg-fast-export.sh.
* `git status` reports that all files are scheduled for deletion after
the initial conversion.
By design fast export does not touch your working directory, so to
git it looks like you have deleted all files, when in fact they have
never been checked out. Just do a checkout of the branch you want.

View File

@@ -1,15 +1,17 @@
#!/usr/bin/env python #!/usr/bin/env python2
# Copyright (c) 2007, 2008 Rocco Rutte <pdmef@gmx.net> and others. # Copyright (c) 2007, 2008 Rocco Rutte <pdmef@gmx.net> and others.
# License: MIT <http://www.opensource.org/licenses/mit-license.php> # License: MIT <http://www.opensource.org/licenses/mit-license.php>
from mercurial import node from mercurial import node
from mercurial.scmutil import revsymbol
from hg2git import setup_repo,fixup_user,get_branch,get_changeset from hg2git import setup_repo,fixup_user,get_branch,get_changeset
from hg2git import load_cache,save_cache,get_git_sha1,set_default_branch,set_origin_name from hg2git import load_cache,save_cache,get_git_sha1,set_default_branch,set_origin_name
from optparse import OptionParser from optparse import OptionParser
import re import re
import sys import sys
import os import os
import pluginloader
if sys.platform == "win32": if sys.platform == "win32":
# On Windows, sys.stdout is initially opened in text mode, which means that # On Windows, sys.stdout is initially opened in text mode, which means that
@@ -26,6 +28,13 @@ cfg_checkpoint_count=0
# write some progress message every this many file contents written # write some progress message every this many file contents written
cfg_export_boundary=1000 cfg_export_boundary=1000
subrepo_cache={}
submodule_mappings=None
# True if fast export should automatically try to sanitize
# author/branch/tag names.
auto_sanitize = None
def gitmode(flags): def gitmode(flags):
return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644' return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644'
@@ -78,7 +87,7 @@ def get_filechanges(repo,revision,parents,mleft):
l,c,r=[],[],[] l,c,r=[],[],[]
for p in parents: for p in parents:
if p<0: continue if p<0: continue
mright=repo.changectx(p).manifest() mright=revsymbol(repo,str(p)).manifest()
l,c,r=split_dict(mleft,mright,l,c,r) l,c,r=split_dict(mleft,mright,l,c,r)
l.sort() l.sort()
c.sort() c.sort()
@@ -122,19 +131,93 @@ def get_author(logmessage,committer,authors):
return r return r
return committer return committer
def export_file_contents(ctx,manifest,files,hgtags,encoding=''): def remove_gitmodules(ctx):
"""Removes all submodules of ctx parents"""
# Removing all submoduies coming from all parents is safe, as the submodules
# of the current commit will be re-added below. A possible optimization would
# be to only remove the submodules of the first parent.
for parent_ctx in ctx.parents():
for submodule in parent_ctx.substate.keys():
wr('D %s' % submodule)
wr('D .gitmodules')
def refresh_git_submodule(name,subrepo_info):
wr('M 160000 %s %s' % (subrepo_info[1],name))
sys.stderr.write("Adding/updating submodule %s, revision %s\n"
% (name,subrepo_info[1]))
return '[submodule "%s"]\n\tpath = %s\n\turl = %s\n' % (name,name,
subrepo_info[0])
def refresh_hg_submodule(name,subrepo_info):
gitRepoLocation=submodule_mappings[name] + "/.git"
# Populate the cache to map mercurial revision to git revision
if not name in subrepo_cache:
subrepo_cache[name]=(load_cache(gitRepoLocation+"/hg2git-mapping"),
load_cache(gitRepoLocation+"/hg2git-marks",
lambda s: int(s)-1))
(mapping_cache,marks_cache)=subrepo_cache[name]
subrepo_hash=subrepo_info[1]
if subrepo_hash in mapping_cache:
revnum=mapping_cache[subrepo_hash]
gitSha=marks_cache[int(revnum)]
wr('M 160000 %s %s' % (gitSha,name))
sys.stderr.write("Adding/updating submodule %s, revision %s->%s\n"
% (name,subrepo_hash,gitSha))
return '[submodule "%s"]\n\tpath = %s\n\turl = %s\n' % (name,name,
submodule_mappings[name])
else:
sys.stderr.write("Warning: Could not find hg revision %s for %s in git %s\n" %
(subrepo_hash,name,gitRepoLocation))
return ''
def refresh_gitmodules(ctx):
"""Updates list of ctx submodules according to .hgsubstate file"""
remove_gitmodules(ctx)
gitmodules=""
# Create the .gitmodules file and all submodules
for name,subrepo_info in ctx.substate.items():
if subrepo_info[2]=='git':
gitmodules+=refresh_git_submodule(name,subrepo_info)
elif submodule_mappings and name in submodule_mappings:
gitmodules+=refresh_hg_submodule(name,subrepo_info)
if len(gitmodules):
wr('M 100644 inline .gitmodules')
wr('data %d' % (len(gitmodules)+1))
wr(gitmodules)
def export_file_contents(ctx,manifest,files,hgtags,encoding='',plugins={}):
count=0 count=0
max=len(files) max=len(files)
is_submodules_refreshed=False
for file in files: for file in files:
if not is_submodules_refreshed and (file=='.hgsub' or file=='.hgsubstate'):
is_submodules_refreshed=True
refresh_gitmodules(ctx)
# Skip .hgtags files. They only get us in trouble. # Skip .hgtags files. They only get us in trouble.
if not hgtags and file == ".hgtags": if not hgtags and file == ".hgtags":
sys.stderr.write('Skip %s\n' % (file)) sys.stderr.write('Skip %s\n' % (file))
continue continue
d=ctx.filectx(file).data()
if encoding: if encoding:
filename=file.decode(encoding).encode('utf8') filename=file.decode(encoding).encode('utf8')
else: else:
filename=file filename=file
if '.git' in filename.split(os.path.sep):
sys.stderr.write('Ignoring file %s which cannot be tracked by git\n' % filename)
continue
file_ctx=ctx.filectx(file)
d=file_ctx.data()
if plugins and plugins['file_data_filters']:
file_data = {'filename':filename,'file_ctx':file_ctx,'data':d}
for filter in plugins['file_data_filters']:
filter(file_data)
d=file_data['data']
filename=file_data['filename']
file_ctx=file_data['file_ctx']
wr('M %s inline %s' % (gitmode(manifest.flags(file)), wr('M %s inline %s' % (gitmode(manifest.flags(file)),
strip_leading_slash(filename))) strip_leading_slash(filename)))
wr('data %d' % len(d)) # had some trouble with size() wr('data %d' % len(d)) # had some trouble with size()
@@ -166,6 +249,8 @@ def sanitize_name(name,what="branch", mapping={}):
if name[0] == '.': return '_'+name[1:] if name[0] == '.': return '_'+name[1:]
return name return name
if not auto_sanitize:
return mapping.get(name,name)
n=mapping.get(name,name) n=mapping.get(name,name)
p=re.compile('([[ ~^:?\\\\*]|\.\.)') p=re.compile('([[ ~^:?\\\\*]|\.\.)')
n=p.sub('_', n) n=p.sub('_', n)
@@ -184,7 +269,8 @@ def strip_leading_slash(filename):
return filename return filename
def export_commit(ui,repo,revision,old_marks,max,count,authors, def export_commit(ui,repo,revision,old_marks,max,count,authors,
branchesmap,sob,brmap,hgtags,encoding='',fn_encoding=''): branchesmap,sob,brmap,hgtags,encoding='',fn_encoding='',
plugins={}):
def get_branchname(name): def get_branchname(name):
if brmap.has_key(name): if brmap.has_key(name):
return brmap[name] return brmap[name]
@@ -193,10 +279,22 @@ def export_commit(ui,repo,revision,old_marks,max,count,authors,
return n return n
(revnode,_,user,(time,timezone),files,desc,branch,_)=get_changeset(ui,repo,revision,authors,encoding) (revnode,_,user,(time,timezone),files,desc,branch,_)=get_changeset(ui,repo,revision,authors,encoding)
if repo[revnode].hidden():
return count
branch=get_branchname(branch) branch=get_branchname(branch)
parents = [p for p in repo.changelog.parentrevs(revision) if p >= 0] parents = [p for p in repo.changelog.parentrevs(revision) if p >= 0]
author = get_author(desc,user,authors)
if plugins and plugins['commit_message_filters']:
commit_data = {'branch': branch, 'parents': parents, 'author': author, 'desc': desc}
for filter in plugins['commit_message_filters']:
filter(commit_data)
branch = commit_data['branch']
parents = commit_data['parents']
author = commit_data['author']
desc = commit_data['desc']
if len(parents)==0 and revision != 0: if len(parents)==0 and revision != 0:
wr('reset refs/heads/%s' % branch) wr('reset refs/heads/%s' % branch)
@@ -204,13 +302,13 @@ def export_commit(ui,repo,revision,old_marks,max,count,authors,
wr('commit refs/heads/%s' % branch) wr('commit refs/heads/%s' % branch)
wr('mark :%d' % (revision+1)) wr('mark :%d' % (revision+1))
if sob: if sob:
wr('author %s %d %s' % (get_author(desc,user,authors),time,timezone)) wr('author %s %d %s' % (author,time,timezone))
wr('committer %s %d %s' % (user,time,timezone)) wr('committer %s %d %s' % (user,time,timezone))
wr('data %d' % (len(desc)+1)) # wtf? wr('data %d' % (len(desc)+1)) # wtf?
wr(desc) wr(desc)
wr() wr()
ctx=repo.changectx(str(revision)) ctx=revsymbol(repo,str(revision))
man=ctx.manifest() man=ctx.manifest()
added,changed,removed,type=[],[],[],'' added,changed,removed,type=[],[],[],''
@@ -225,8 +323,8 @@ def export_commit(ui,repo,revision,old_marks,max,count,authors,
# later non-merge revision: feed in changed manifest # later non-merge revision: feed in changed manifest
# if we have exactly one parent, just take the changes from the # if we have exactly one parent, just take the changes from the
# manifest without expensively comparing checksums # manifest without expensively comparing checksums
f=repo.status(repo.lookup(parents[0]),revnode)[:3] f=repo.status(parents[0],revnode)
added,changed,removed=f[1],f[0],f[2] added,changed,removed=f.added,f.modified,f.removed
type='simple delta' type='simple delta'
else: # a merge with two parents else: # a merge with two parents
wr('merge %s' % revnum_to_revref(parents[1], old_marks)) wr('merge %s' % revnum_to_revref(parents[1], old_marks))
@@ -239,20 +337,24 @@ def export_commit(ui,repo,revision,old_marks,max,count,authors,
sys.stderr.write('%s: Exporting %s revision %d/%d with %d/%d/%d added/changed/removed files\n' % sys.stderr.write('%s: Exporting %s revision %d/%d with %d/%d/%d added/changed/removed files\n' %
(branch,type,revision+1,max,len(added),len(changed),len(removed))) (branch,type,revision+1,max,len(added),len(changed),len(removed)))
if fn_encoding: for filename in removed:
removed=[r.decode(fn_encoding).encode('utf8') for r in removed] if fn_encoding:
filename=filename.decode(fn_encoding).encode('utf8')
filename=strip_leading_slash(filename)
if filename=='.hgsub':
remove_gitmodules(ctx)
wr('D %s' % filename)
removed=[strip_leading_slash(x) for x in removed] export_file_contents(ctx,man,added,hgtags,fn_encoding,plugins)
export_file_contents(ctx,man,changed,hgtags,fn_encoding,plugins)
map(lambda r: wr('D %s' % r),removed)
export_file_contents(ctx,man,added,hgtags,fn_encoding)
export_file_contents(ctx,man,changed,hgtags,fn_encoding)
wr() wr()
return checkpoint(count) return checkpoint(count)
def export_note(ui,repo,revision,count,authors,encoding,is_first): def export_note(ui,repo,revision,count,authors,encoding,is_first):
(revnode,_,user,(time,timezone),_,_,_,_)=get_changeset(ui,repo,revision,authors,encoding) (revnode,_,user,(time,timezone),_,_,_,_)=get_changeset(ui,repo,revision,authors,encoding)
if repo[revnode].hidden():
return count
parents = [p for p in repo.changelog.parentrevs(revision) if p >= 0] parents = [p for p in repo.changelog.parentrevs(revision) if p >= 0]
@@ -262,7 +364,7 @@ def export_note(ui,repo,revision,count,authors,encoding,is_first):
if is_first: if is_first:
wr('from refs/notes/hg^0') wr('from refs/notes/hg^0')
wr('N inline :%d' % (revision+1)) wr('N inline :%d' % (revision+1))
hg_hash=repo.changectx(str(revision)).hex() hg_hash=revsymbol(repo,str(revision)).hex()
wr('data %d' % (len(hg_hash))) wr('data %d' % (len(hg_hash)))
wr_no_nl(hg_hash) wr_no_nl(hg_hash)
wr() wr()
@@ -370,7 +472,7 @@ def verify_heads(ui,repo,cache,force,branchesmap):
# verify that branch has exactly one head # verify that branch has exactly one head
t={} t={}
for h in repo.heads(): for h in repo.filtered('visible').heads():
(_,_,_,_,_,_,branch,_)=get_changeset(ui,repo,h) (_,_,_,_,_,_,branch,_)=get_changeset(ui,repo,h)
if t.get(branch,False): if t.get(branch,False):
sys.stderr.write('Error: repository has at least one unnamed head: hg r%s\n' % sys.stderr.write('Error: repository has at least one unnamed head: hg r%s\n' %
@@ -382,7 +484,8 @@ def verify_heads(ui,repo,cache,force,branchesmap):
def hg2git(repourl,m,marksfile,mappingfile,headsfile,tipfile, def hg2git(repourl,m,marksfile,mappingfile,headsfile,tipfile,
authors={},branchesmap={},tagsmap={}, authors={},branchesmap={},tagsmap={},
sob=False,force=False,hgtags=False,notes=False,encoding='',fn_encoding=''): sob=False,force=False,hgtags=False,notes=False,encoding='',fn_encoding='',
plugins={}):
def check_cache(filename, contents): def check_cache(filename, contents):
if len(contents) == 0: if len(contents) == 0:
sys.stderr.write('Warning: %s does not contain any data, this will probably make an incremental import fail\n' % filename) sys.stderr.write('Warning: %s does not contain any data, this will probably make an incremental import fail\n' % filename)
@@ -417,14 +520,28 @@ def hg2git(repourl,m,marksfile,mappingfile,headsfile,tipfile,
for rev in range(0,max): for rev in range(0,max):
(revnode,_,_,_,_,_,_,_)=get_changeset(ui,repo,rev,authors) (revnode,_,_,_,_,_,_,_)=get_changeset(ui,repo,rev,authors)
if repo[revnode].hidden():
continue
mapping_cache[revnode.encode('hex_codec')] = str(rev) mapping_cache[revnode.encode('hex_codec')] = str(rev)
if submodule_mappings:
# Make sure that all submodules are registered in the submodule-mappings file
for rev in range(0,max):
ctx=revsymbol(repo,str(rev))
if ctx.hidden():
continue
if ctx.substate:
for key in ctx.substate:
if key not in submodule_mappings:
sys.stderr.write("Error: %s not found in submodule-mappings\n" % (key))
return 1
c=0 c=0
brmap={} brmap={}
for rev in range(min,max): for rev in range(min,max):
c=export_commit(ui,repo,rev,old_marks,max,c,authors,branchesmap, c=export_commit(ui,repo,rev,old_marks,max,c,authors,branchesmap,
sob,brmap,hgtags,encoding,fn_encoding) sob,brmap,hgtags,encoding,fn_encoding,
plugins)
if notes: if notes:
for rev in range(min,max): for rev in range(min,max):
c=export_note(ui,repo,rev,c,authors, encoding, rev == min and min != 0) c=export_note(ui,repo,rev,c,authors, encoding, rev == min and min != 0)
@@ -448,6 +565,9 @@ if __name__=='__main__':
parser=OptionParser() parser=OptionParser()
parser.add_option("-n", "--no-auto-sanitize",action="store_false",
dest="auto_sanitize",default=True,
help="Do not perform built-in (broken in many cases) sanitizing of names")
parser.add_option("-m","--max",type="int",dest="max", parser.add_option("-m","--max",type="int",dest="max",
help="Maximum hg revision to import") help="Maximum hg revision to import")
parser.add_option("--mapping",dest="mappingfile", parser.add_option("--mapping",dest="mappingfile",
@@ -484,10 +604,19 @@ if __name__=='__main__':
help="Assume file names from Mercurial are encoded in <filename_encoding>") help="Assume file names from Mercurial are encoded in <filename_encoding>")
parser.add_option("--mappings-are-raw",dest="raw_mappings", default=False, parser.add_option("--mappings-are-raw",dest="raw_mappings", default=False,
help="Assume mappings are raw <key>=<value> lines") help="Assume mappings are raw <key>=<value> lines")
parser.add_option("--filter-contents",dest="filter_contents",
help="Pipe contents of each exported file through FILTER_CONTENTS <file-path> <hg-hash> <is-binary>")
parser.add_option("--plugin-path", type="string", dest="pluginpath",
help="Additional search path for plugins ")
parser.add_option("--plugin", action="append", type="string", dest="plugins",
help="Add a plugin with the given init string <name=init>")
parser.add_option("--subrepo-map", type="string", dest="subrepo_map",
help="Provide a mapping file between the subrepository name and the submodule name")
(options,args)=parser.parse_args() (options,args)=parser.parse_args()
m=-1 m=-1
auto_sanitize = options.auto_sanitize
if options.max!=None: m=options.max if options.max!=None: m=options.max
if options.marksfile==None: bail(parser,'--marks') if options.marksfile==None: bail(parser,'--marks')
@@ -496,6 +625,14 @@ if __name__=='__main__':
if options.statusfile==None: bail(parser,'--status') if options.statusfile==None: bail(parser,'--status')
if options.repourl==None: bail(parser,'--repo') if options.repourl==None: bail(parser,'--repo')
if options.subrepo_map:
if not os.path.exists(options.subrepo_map):
sys.stderr.write('Subrepo mapping file not found %s\n'
% options.subrepo_map)
sys.exit(1)
submodule_mappings=load_mapping('subrepo mappings',
options.subrepo_map,False)
a={} a={}
if options.authorfile!=None: if options.authorfile!=None:
a=load_mapping('authors', options.authorfile, options.raw_mappings) a=load_mapping('authors', options.authorfile, options.raw_mappings)
@@ -506,7 +643,7 @@ if __name__=='__main__':
t={} t={}
if options.tagsfile!=None: if options.tagsfile!=None:
t=load_mapping('tags', options.tagsfile, True) t=load_mapping('tags', options.tagsfile, options.raw_mappings)
if options.default_branch!=None: if options.default_branch!=None:
set_default_branch(options.default_branch) set_default_branch(options.default_branch)
@@ -522,8 +659,34 @@ if __name__=='__main__':
if options.fn_encoding!=None: if options.fn_encoding!=None:
fn_encoding=options.fn_encoding fn_encoding=options.fn_encoding
plugins=[]
if options.plugins!=None:
plugins+=options.plugins
if options.filter_contents!=None:
plugins+=['shell_filter_file_contents='+options.filter_contents]
plugins_dict={}
plugins_dict['commit_message_filters']=[]
plugins_dict['file_data_filters']=[]
if plugins and options.pluginpath:
sys.stderr.write('Using additional plugin path: ' + options.pluginpath + '\n')
for plugin in plugins:
split = plugin.split('=')
name, opts = split[0], '='.join(split[1:])
i = pluginloader.get_plugin(name,options.pluginpath)
sys.stderr.write('Loaded plugin ' + i['name'] + ' from path: ' + i['path'] +' with opts: ' + opts + '\n')
plugin = pluginloader.load_plugin(i).build_filter(opts)
if hasattr(plugin,'file_data_filter') and callable(plugin.file_data_filter):
plugins_dict['file_data_filters'].append(plugin.file_data_filter)
if hasattr(plugin, 'commit_message_filter') and callable(plugin.commit_message_filter):
plugins_dict['commit_message_filters'].append(plugin.commit_message_filter)
sys.exit(hg2git(options.repourl,m,options.marksfile,options.mappingfile, sys.exit(hg2git(options.repourl,m,options.marksfile,options.mappingfile,
options.headsfile, options.statusfile, options.headsfile, options.statusfile,
authors=a,branchesmap=b,tagsmap=t, authors=a,branchesmap=b,tagsmap=t,
sob=options.sob,force=options.force,hgtags=options.hgtags, sob=options.sob,force=options.force,hgtags=options.hgtags,
notes=options.notes,encoding=encoding,fn_encoding=fn_encoding)) notes=options.notes,encoding=encoding,fn_encoding=fn_encoding,
plugins=plugins_dict))

View File

@@ -26,7 +26,29 @@ SFX_MARKS="marks"
SFX_HEADS="heads" SFX_HEADS="heads"
SFX_STATE="state" SFX_STATE="state"
GFI_OPTS="" GFI_OPTS=""
PYTHON=${PYTHON:-python}
if [ -z "${PYTHON}" ]; then
# $PYTHON is not set, so we try to find a working python 2.7 to
# use. PEP 394 tells us to use 'python2', otherwise try plain
# 'python'.
if command -v python2 > /dev/null; then
PYTHON="python2"
elif command -v python > /dev/null; then
PYTHON="python"
else
echo "Could not find any python interpreter, please use the 'PYTHON'" \
"environment variable to specify the interpreter to use."
exit 1
fi
fi
# Check that the python specified by the user or autodetected above is
# >= 2.7 and < 3.
if ! ${PYTHON} -c 'import sys; v=sys.version_info; exit(0 if v.major == 2 and v.minor >= 7 else 1)' > /dev/null 2>&1 ; then
echo "${PYTHON} is not a working python 2.7 interpreter, please use the" \
"'PYTHON' environment variable to specify the interpreter to use."
exit 1
fi
USAGE="[--quiet] [-r <repo>] [--force] [-m <max>] [-s] [--hgtags] [-A <file>] [-B <file>] [-T <file>] [-M <name>] [-o <name>] [--hg-hash] [-e <encoding>]" USAGE="[--quiet] [-r <repo>] [--force] [-m <max>] [-s] [--hgtags] [-A <file>] [-B <file>] [-T <file>] [-M <name>] [-o <name>] [--hg-hash] [-e <encoding>]"
LONG_USAGE="Import hg repository <repo> up to either tip or <max> LONG_USAGE="Import hg repository <repo> up to either tip or <max>
@@ -48,6 +70,8 @@ Options:
-B <file> Read branch map from file -B <file> Read branch map from file
-T <file> Read tags map from file -T <file> Read tags map from file
-M <name> Set the default branch name (defaults to 'master') -M <name> Set the default branch name (defaults to 'master')
-n Do not perform built-in (broken in many cases) sanitizing
of branch/tag names.
-o <name> Use <name> as branch namespace to track upstream (eg 'origin') -o <name> Use <name> as branch namespace to track upstream (eg 'origin')
--hg-hash Annotate commits with the hg hash as git notes in the --hg-hash Annotate commits with the hg hash as git notes in the
hg namespace. hg namespace.
@@ -56,6 +80,10 @@ Options:
--fe <filename_encoding> Assume filenames from Mercurial are encoded --fe <filename_encoding> Assume filenames from Mercurial are encoded
in <filename_encoding> in <filename_encoding>
--mappings-are-raw Assume mappings are raw <key>=<value> lines --mappings-are-raw Assume mappings are raw <key>=<value> lines
--filter-contents <cmd> Pipe contents of each exported file through <cmd>
with <file-path> <hg-hash> <is-binary> as arguments
--plugin <plugin=init> Add a plugin with the given init string (repeatable)
--plugin-path <plugin-path> Add an additional plugin lookup path
" "
case "$1" in case "$1" in
-h|--help) -h|--help)

View File

@@ -1,9 +1,12 @@
#!/usr/bin/env python #!/usr/bin/env python2
# Copyright (c) 2007, 2008 Rocco Rutte <pdmef@gmx.net> and others. # Copyright (c) 2007, 2008 Rocco Rutte <pdmef@gmx.net> and others.
# License: MIT <http://www.opensource.org/licenses/mit-license.php> # License: MIT <http://www.opensource.org/licenses/mit-license.php>
from mercurial import hg,util,ui,templatefilters from mercurial import hg,util,ui,templatefilters
from mercurial import error as hgerror
from mercurial.scmutil import revsymbol,binnode
import re import re
import os import os
import sys import sys
@@ -32,7 +35,9 @@ def setup_repo(url):
except TypeError: except TypeError:
myui=ui.ui() myui=ui.ui()
myui.setconfig('ui', 'interactive', 'off') myui.setconfig('ui', 'interactive', 'off')
return myui,hg.repository(myui,url) # Avoids a warning when the repository has obsolete markers
myui.setconfig('experimental', 'evolution.createmarkers', True)
return myui,hg.repository(myui,url).unfiltered()
def fixup_user(user,authors): def fixup_user(user,authors):
user=user.strip("\"") user=user.strip("\"")
@@ -46,7 +51,7 @@ def fixup_user(user,authors):
# and mail from hg helpers. this seems to work pretty well. # and mail from hg helpers. this seems to work pretty well.
# if email doesn't contain @, replace it with devnull@localhost # if email doesn't contain @, replace it with devnull@localhost
name=templatefilters.person(user) name=templatefilters.person(user)
mail='<%s>' % util.email(user) mail='<%s>' % templatefilters.email(user)
if '@' not in mail: if '@' not in mail:
mail = '<devnull@localhost>' mail = '<devnull@localhost>'
else: else:
@@ -69,7 +74,15 @@ def get_branch(name):
return name return name
def get_changeset(ui,repo,revision,authors={},encoding=''): def get_changeset(ui,repo,revision,authors={},encoding=''):
node=repo.lookup(revision) # Starting with Mercurial 4.6 lookup no longer accepts raw hashes
# for lookups. Work around it by changing our behaviour depending on
# how it fails
try:
node=repo.lookup(revision)
except hgerror.ProgrammingError:
node=binnode(revsymbol(repo,str(revision))) # We were given a numeric rev
except hgerror.RepoLookupError:
node=revision # We got a raw hash
(manifest,user,(time,timezone),files,desc,extra)=repo.changelog.read(node) (manifest,user,(time,timezone),files,desc,extra)=repo.changelog.read(node)
if encoding: if encoding:
user=user.decode(encoding).encode('utf8') user=user.decode(encoding).encode('utf8')

19
pluginloader/__init__.py Normal file
View File

@@ -0,0 +1,19 @@
import os
import imp
PluginFolder = os.path.join(os.path.dirname(os.path.realpath(__file__)),"..","plugins")
MainModule = "__init__"
def get_plugin(name, plugin_path):
search_dirs = [PluginFolder]
if plugin_path:
search_dirs = [plugin_path] + search_dirs
for dir in search_dirs:
location = os.path.join(dir, name)
if not os.path.isdir(location) or not MainModule + ".py" in os.listdir(location):
continue
info = imp.find_module(MainModule, [location])
return {"name": name, "info": info, "path": location}
raise Exception("Could not find plugin with name " + name)
def load_plugin(plugin):
return imp.load_module(MainModule, *plugin["info"])

View File

@@ -0,0 +1,20 @@
## Branch Name in Commit Message
Mercurial has a much stronger notion of branches than Git,
and some parties may not wish to lose the branch information
during the migration to Git. You can use this plugin to either
prepend or append the branch name from the mercurial
commit into the commit message in Git.
Valid arguments are:
- `start`: write the branch name at the start of the commit
- `end`: write the branch name at the end of the commit
- `sameline`: if `start` specified, put a colon and a space
after the branch name, such that the commit message reads
`branch_name: first line of commit message`. Otherwise, the
branch name is on the first line of the commit message by itself.
- `skipmaster`: Don't write the branch name if the branch is `master`.
To use the plugin, add
`--plugin branch_name_in_commit=<comma_separated_list_of_args>`.

View File

@@ -0,0 +1,23 @@
def build_filter(args):
return Filter(args)
class Filter:
def __init__(self, args):
args = {arg: True for arg in args.split(',')}
self.start = args.pop('start', False)
self.end = args.pop('end', False)
self.sameline = args.pop('sameline', False)
self.skip_master = args.pop('skipmaster', False)
if self.sameline and not self.start:
raise ValueError("sameline option only allowed if 'start' given")
if args:
raise ValueError("Unknown args: " + ','.join(args))
def commit_message_filter(self, commit_data):
if not (self.skip_master and commit_data['branch'] == 'master'):
if self.start:
sep = ': ' if self.sameline else '\n'
commit_data['desc'] = commit_data['branch'] + sep + commit_data['desc']
if self.end:
commit_data['desc'] = commit_data['desc'] + '\n' + commit_data['branch']

View File

@@ -0,0 +1,9 @@
## Dos2unix filter
This plugin converts CRLF line ending to LF in text files in the repo.
It is recommended that you have a .gitattributes file that maintains
the usage of LF endings going forward, for after you have converted your
repository.
To use the plugin, add
`--plugin dos2unix`.

View File

@@ -0,0 +1,11 @@
def build_filter(args):
return Filter(args)
class Filter():
def __init__(self, args):
pass
def file_data_filter(self,file_data):
file_ctx = file_data['file_ctx']
if not file_ctx.isbinary():
file_data['data'] = file_data['data'].replace('\r\n', '\n')

View File

@@ -0,0 +1,19 @@
## Issue Prefix
When migrating to other source code hosting sites, there are cases where a
project maintainer might want to reset their issue tracker and not have old
issue numbers in commit messages referring to the wrong issue. One way around
this is to prefix issue numbers with some other string.
If migrating to GitHub, this issue prefixing can be paired with GitHub's
autolinking capabilitiy to link back to a different issue tracker:
https://help.github.com/en/github/administering-a-repository/configuring-autolinks-to-reference-external-resources
To use this plugin, add:
`--plugin=issue_prefix=<some_prefix>`
Example:
`--plugin=issue_prefix=BB-`
This will prefix issue numbers with the string `BB-`. Example: `#123` will
change to `#BB-123`.

View File

@@ -0,0 +1,15 @@
# encoding=UTF-8
"""__init__.py"""
import re
def build_filter(args):
return Filter(args)
class Filter:
def __init__(self, args):
self.prefix = args
def commit_message_filter(self, commit_data):
for match in re.findall('#[1-9][0-9]+', commit_data['desc']):
commit_data['desc'] = commit_data['desc'].replace(
match, '#%s%s' % (self.prefix, match[1:]))

View File

@@ -0,0 +1,23 @@
## Overwrite Null Commit Messages
There are cases (such as when creating a new, empty snippet on bitbucket
before they deprecated mercurial repositories) where you could create a
new repo with a single commit in it, but the message would be null. Then,
when attempting to convert this repository to a git repo and pushing to
a new host, the git push would fail with an error like this:
error: a NUL byte in commit log message not allowed
To get around this, you may provide a string that will be used in place of
a null byte in commit messages.
To use the plugin, add
--plugin overwrite_null_messages=""
This will use the default commit message `"<empty commit message>"`.
Or to specify a different commit message, you may pass this in at the
command line like so:
--plugin overwrite_null_messages="use this message instead"

View File

@@ -0,0 +1,16 @@
def build_filter(args):
return Filter(args)
class Filter:
def __init__(self, args):
if args == '':
message = '<empty commit message>'
else:
message = args
self.message = message
def commit_message_filter(self,commit_data):
# Only write the commit message if the recorded commit
# message is null.
if commit_data['desc'] == '\x00':
commit_data['desc'] = self.message

View File

@@ -0,0 +1,30 @@
## Shell Script File Filter
This plugin uses shell scripts in order to perform filtering of files.
If your preferred scripting is done via shell, this tool is for you.
Be noted, though, that this method can cause an order of magnitude slow
down. For small repositories, this wont be an issue.
To use the plugin, add
`--plugin shell_filter_file_contents=path/to/shell/script.sh`.
The filter script is supplied to the plugin option after the plugin name,
which is in turned passed to the plugin initialization. hg-fast-export
runs the filter for each exported file, pipes its content to the filter's
standard input, and uses the filter's standard output in place
of the file's original content. An example use of this feature
is to convert line endings in text files from CRLF to git's preferred LF,
although this task is faster performed using the native plugin.
The script is called with the following syntax:
`FILTER_CONTENTS <file-path> <hg-hash> <is-binary>`
```
-- Start of crlf-filter.sh --
#!/bin/sh
# $1 = pathname of exported file relative to the root of the repo
# $2 = Mercurial's hash of the file
# $3 = "1" if Mercurial reports the file as binary, otherwise "0"
if [ "$3" == "1" ]; then cat; else dos2unix; fi
-- End of crlf-filter.sh --
```

View File

@@ -0,0 +1,28 @@
#Pipe contents of each exported file through FILTER_CONTENTS <file-path> <hg-hash> <is-binary>"
import subprocess
import shlex
import sys
from mercurial import node
def build_filter(args):
return Filter(args)
class Filter:
def __init__(self, args):
self.filter_contents = shlex.split(args)
def file_data_filter(self,file_data):
d = file_data['data']
file_ctx = file_data['file_ctx']
filename = file_data['filename']
filter_cmd = self.filter_contents + [filename, node.hex(file_ctx.filenode()), '1' if file_ctx.isbinary() else '0']
try:
filter_proc = subprocess.Popen(filter_cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
d, _ = filter_proc.communicate(d)
except:
sys.stderr.write('Running filter-contents %s:\n' % filter_cmd)
raise
filter_ret = filter_proc.poll()
if filter_ret:
raise subprocess.CalledProcessError(filter_ret, filter_cmd)
file_data['data'] = d