Show More
@@ -1,176 +1,176 | |||||
1 | # |
|
1 | # | |
2 | # Perforce source for convert extension. |
|
2 | # Perforce source for convert extension. | |
3 | # |
|
3 | # | |
4 | # Copyright 2009, Frank Kingswood <frank@kingswood-consulting.co.uk> |
|
4 | # Copyright 2009, Frank Kingswood <frank@kingswood-consulting.co.uk> | |
5 | # |
|
5 | # | |
6 | # This software may be used and distributed according to the terms |
|
6 | # This software may be used and distributed according to the terms | |
7 | # of the GNU General Public License, incorporated herein by reference. |
|
7 | # of the GNU General Public License, incorporated herein by reference. | |
8 | # |
|
8 | # | |
9 |
|
9 | |||
10 | from mercurial import util |
|
10 | from mercurial import util | |
11 | from mercurial.i18n import _ |
|
11 | from mercurial.i18n import _ | |
12 |
|
12 | |||
13 | from common import commit, converter_source, checktool |
|
13 | from common import commit, converter_source, checktool | |
14 | import marshal |
|
14 | import marshal | |
15 |
|
15 | |||
16 | def loaditer(f): |
|
16 | def loaditer(f): | |
17 | "Yield the dictionary objects generated by p4" |
|
17 | "Yield the dictionary objects generated by p4" | |
18 | try: |
|
18 | try: | |
19 | while True: |
|
19 | while True: | |
20 | d = marshal.load(f) |
|
20 | d = marshal.load(f) | |
21 | if not d: |
|
21 | if not d: | |
22 | break |
|
22 | break | |
23 | yield d |
|
23 | yield d | |
24 | except EOFError: |
|
24 | except EOFError: | |
25 | pass |
|
25 | pass | |
26 |
|
26 | |||
27 | class p4_source(converter_source): |
|
27 | class p4_source(converter_source): | |
28 | def __init__(self, ui, path, rev=None): |
|
28 | def __init__(self, ui, path, rev=None): | |
29 | super(p4_source, self).__init__(ui, path, rev=rev) |
|
29 | super(p4_source, self).__init__(ui, path, rev=rev) | |
30 |
|
30 | |||
31 | checktool('p4') |
|
31 | checktool('p4', abort=False) | |
32 |
|
32 | |||
33 | self.p4changes = {} |
|
33 | self.p4changes = {} | |
34 | self.heads = {} |
|
34 | self.heads = {} | |
35 | self.changeset = {} |
|
35 | self.changeset = {} | |
36 | self.files = {} |
|
36 | self.files = {} | |
37 | self.tags = {} |
|
37 | self.tags = {} | |
38 | self.lastbranch = {} |
|
38 | self.lastbranch = {} | |
39 | self.parent = {} |
|
39 | self.parent = {} | |
40 | self.encoding = "latin_1" |
|
40 | self.encoding = "latin_1" | |
41 | self.depotname = {} # mapping from local name to depot name |
|
41 | self.depotname = {} # mapping from local name to depot name | |
42 | self.modecache = {} |
|
42 | self.modecache = {} | |
43 |
|
43 | |||
44 | self._parse(ui, path) |
|
44 | self._parse(ui, path) | |
45 |
|
45 | |||
46 | def _parse_view(self, path): |
|
46 | def _parse_view(self, path): | |
47 | "Read changes affecting the path" |
|
47 | "Read changes affecting the path" | |
48 | cmd = "p4 -G changes -s submitted '%s'" % path |
|
48 | cmd = "p4 -G changes -s submitted '%s'" % path | |
49 | stdout = util.popen(cmd) |
|
49 | stdout = util.popen(cmd) | |
50 | for d in loaditer(stdout): |
|
50 | for d in loaditer(stdout): | |
51 | c = d.get("change", None) |
|
51 | c = d.get("change", None) | |
52 | if c: |
|
52 | if c: | |
53 | self.p4changes[c] = True |
|
53 | self.p4changes[c] = True | |
54 |
|
54 | |||
55 | def _parse(self, ui, path): |
|
55 | def _parse(self, ui, path): | |
56 | "Prepare list of P4 filenames and revisions to import" |
|
56 | "Prepare list of P4 filenames and revisions to import" | |
57 | ui.status(_('reading p4 views\n')) |
|
57 | ui.status(_('reading p4 views\n')) | |
58 |
|
58 | |||
59 | # read client spec or view |
|
59 | # read client spec or view | |
60 | if "/" in path: |
|
60 | if "/" in path: | |
61 | self._parse_view(path) |
|
61 | self._parse_view(path) | |
62 | if path.startswith("//") and path.endswith("/..."): |
|
62 | if path.startswith("//") and path.endswith("/..."): | |
63 | views = {path[:-3]:""} |
|
63 | views = {path[:-3]:""} | |
64 | else: |
|
64 | else: | |
65 | views = {"//": ""} |
|
65 | views = {"//": ""} | |
66 | else: |
|
66 | else: | |
67 | cmd = "p4 -G client -o '%s'" % path |
|
67 | cmd = "p4 -G client -o '%s'" % path | |
68 | clientspec = marshal.load(util.popen(cmd)) |
|
68 | clientspec = marshal.load(util.popen(cmd)) | |
69 |
|
69 | |||
70 | views = {} |
|
70 | views = {} | |
71 | for client in clientspec: |
|
71 | for client in clientspec: | |
72 | if client.startswith("View"): |
|
72 | if client.startswith("View"): | |
73 | sview, cview = clientspec[client].split() |
|
73 | sview, cview = clientspec[client].split() | |
74 | self._parse_view(sview) |
|
74 | self._parse_view(sview) | |
75 | if sview.endswith("...") and cview.endswith("..."): |
|
75 | if sview.endswith("...") and cview.endswith("..."): | |
76 | sview = sview[:-3] |
|
76 | sview = sview[:-3] | |
77 | cview = cview[:-3] |
|
77 | cview = cview[:-3] | |
78 | cview = cview[2:] |
|
78 | cview = cview[2:] | |
79 | cview = cview[cview.find("/") + 1:] |
|
79 | cview = cview[cview.find("/") + 1:] | |
80 | views[sview] = cview |
|
80 | views[sview] = cview | |
81 |
|
81 | |||
82 | # list of changes that affect our source files |
|
82 | # list of changes that affect our source files | |
83 | self.p4changes = self.p4changes.keys() |
|
83 | self.p4changes = self.p4changes.keys() | |
84 | self.p4changes.sort(key=int) |
|
84 | self.p4changes.sort(key=int) | |
85 |
|
85 | |||
86 | # list with depot pathnames, longest first |
|
86 | # list with depot pathnames, longest first | |
87 | vieworder = views.keys() |
|
87 | vieworder = views.keys() | |
88 | vieworder.sort(key=lambda x: -len(x)) |
|
88 | vieworder.sort(key=lambda x: -len(x)) | |
89 |
|
89 | |||
90 | # handle revision limiting |
|
90 | # handle revision limiting | |
91 | startrev = self.ui.config('convert', 'p4.startrev', default=0) |
|
91 | startrev = self.ui.config('convert', 'p4.startrev', default=0) | |
92 | self.p4changes = [x for x in self.p4changes |
|
92 | self.p4changes = [x for x in self.p4changes | |
93 | if ((not startrev or int(x) >= int(startrev)) and |
|
93 | if ((not startrev or int(x) >= int(startrev)) and | |
94 | (not self.rev or int(x) <= int(self.rev)))] |
|
94 | (not self.rev or int(x) <= int(self.rev)))] | |
95 |
|
95 | |||
96 | # now read the full changelists to get the list of file revisions |
|
96 | # now read the full changelists to get the list of file revisions | |
97 | ui.status(_('collecting p4 changelists\n')) |
|
97 | ui.status(_('collecting p4 changelists\n')) | |
98 | lastid = None |
|
98 | lastid = None | |
99 | for change in self.p4changes: |
|
99 | for change in self.p4changes: | |
100 | cmd = "p4 -G describe %s" % change |
|
100 | cmd = "p4 -G describe %s" % change | |
101 | stdout = util.popen(cmd) |
|
101 | stdout = util.popen(cmd) | |
102 | d = marshal.load(stdout) |
|
102 | d = marshal.load(stdout) | |
103 |
|
103 | |||
104 | desc = self.recode(d["desc"]) |
|
104 | desc = self.recode(d["desc"]) | |
105 | shortdesc = desc.split("\n", 1)[0] |
|
105 | shortdesc = desc.split("\n", 1)[0] | |
106 | t = '%s %s' % (d["change"], repr(shortdesc)[1:-1]) |
|
106 | t = '%s %s' % (d["change"], repr(shortdesc)[1:-1]) | |
107 | ui.status(util.ellipsis(t, 80) + '\n') |
|
107 | ui.status(util.ellipsis(t, 80) + '\n') | |
108 |
|
108 | |||
109 | if lastid: |
|
109 | if lastid: | |
110 | parents = [lastid] |
|
110 | parents = [lastid] | |
111 | else: |
|
111 | else: | |
112 | parents = [] |
|
112 | parents = [] | |
113 |
|
113 | |||
114 | date = (int(d["time"]), 0) # timezone not set |
|
114 | date = (int(d["time"]), 0) # timezone not set | |
115 | c = commit(author=self.recode(d["user"]), date=util.datestr(date), |
|
115 | c = commit(author=self.recode(d["user"]), date=util.datestr(date), | |
116 | parents=parents, desc=desc, branch='', extra={"p4": change}) |
|
116 | parents=parents, desc=desc, branch='', extra={"p4": change}) | |
117 |
|
117 | |||
118 | files = [] |
|
118 | files = [] | |
119 | i = 0 |
|
119 | i = 0 | |
120 | while ("depotFile%d" % i) in d and ("rev%d" % i) in d: |
|
120 | while ("depotFile%d" % i) in d and ("rev%d" % i) in d: | |
121 | oldname = d["depotFile%d" % i] |
|
121 | oldname = d["depotFile%d" % i] | |
122 | filename = None |
|
122 | filename = None | |
123 | for v in vieworder: |
|
123 | for v in vieworder: | |
124 | if oldname.startswith(v): |
|
124 | if oldname.startswith(v): | |
125 | filename = views[v] + oldname[len(v):] |
|
125 | filename = views[v] + oldname[len(v):] | |
126 | break |
|
126 | break | |
127 | if filename: |
|
127 | if filename: | |
128 | files.append((filename, d["rev%d" % i])) |
|
128 | files.append((filename, d["rev%d" % i])) | |
129 | self.depotname[filename] = oldname |
|
129 | self.depotname[filename] = oldname | |
130 | i += 1 |
|
130 | i += 1 | |
131 | self.changeset[change] = c |
|
131 | self.changeset[change] = c | |
132 | self.files[change] = files |
|
132 | self.files[change] = files | |
133 | lastid = change |
|
133 | lastid = change | |
134 |
|
134 | |||
135 | if lastid: |
|
135 | if lastid: | |
136 | self.heads = [lastid] |
|
136 | self.heads = [lastid] | |
137 |
|
137 | |||
138 | def getheads(self): |
|
138 | def getheads(self): | |
139 | return self.heads |
|
139 | return self.heads | |
140 |
|
140 | |||
141 | def getfile(self, name, rev): |
|
141 | def getfile(self, name, rev): | |
142 | cmd = "p4 -G print '%s#%s'" % (self.depotname[name], rev) |
|
142 | cmd = "p4 -G print '%s#%s'" % (self.depotname[name], rev) | |
143 | stdout = util.popen(cmd) |
|
143 | stdout = util.popen(cmd) | |
144 |
|
144 | |||
145 | mode = None |
|
145 | mode = None | |
146 | data = "" |
|
146 | data = "" | |
147 |
|
147 | |||
148 | for d in loaditer(stdout): |
|
148 | for d in loaditer(stdout): | |
149 | if d["code"] == "stat": |
|
149 | if d["code"] == "stat": | |
150 | if "+x" in d["type"]: |
|
150 | if "+x" in d["type"]: | |
151 | mode = "x" |
|
151 | mode = "x" | |
152 | else: |
|
152 | else: | |
153 | mode = "" |
|
153 | mode = "" | |
154 | elif d["code"] == "text": |
|
154 | elif d["code"] == "text": | |
155 | data += d["data"] |
|
155 | data += d["data"] | |
156 |
|
156 | |||
157 | if mode is None: |
|
157 | if mode is None: | |
158 | raise IOError() |
|
158 | raise IOError() | |
159 |
|
159 | |||
160 | self.modecache[(name, rev)] = mode |
|
160 | self.modecache[(name, rev)] = mode | |
161 | return data |
|
161 | return data | |
162 |
|
162 | |||
163 | def getmode(self, name, rev): |
|
163 | def getmode(self, name, rev): | |
164 | return self.modecache[(name, rev)] |
|
164 | return self.modecache[(name, rev)] | |
165 |
|
165 | |||
166 | def getchanges(self, rev): |
|
166 | def getchanges(self, rev): | |
167 | return self.files[rev], {} |
|
167 | return self.files[rev], {} | |
168 |
|
168 | |||
169 | def getcommit(self, rev): |
|
169 | def getcommit(self, rev): | |
170 | return self.changeset[rev] |
|
170 | return self.changeset[rev] | |
171 |
|
171 | |||
172 | def gettags(self): |
|
172 | def gettags(self): | |
173 | return self.tags |
|
173 | return self.tags | |
174 |
|
174 | |||
175 | def getchangedfiles(self, rev, i): |
|
175 | def getchangedfiles(self, rev, i): | |
176 | return util.sort([x[0] for x in self.files[rev]]) |
|
176 | return util.sort([x[0] for x in self.files[rev]]) |
@@ -1,46 +1,50 | |||||
1 | #!/bin/sh |
|
1 | #!/bin/sh | |
2 |
|
2 | |||
3 | cat >> $HGRCPATH <<EOF |
|
3 | cat >> $HGRCPATH <<EOF | |
4 | [extensions] |
|
4 | [extensions] | |
5 | convert= |
|
5 | convert= | |
6 | [convert] |
|
6 | [convert] | |
7 | hg.saverev=False |
|
7 | hg.saverev=False | |
8 | EOF |
|
8 | EOF | |
9 |
|
9 | |||
10 | hg help convert |
|
10 | hg help convert | |
11 |
|
11 | |||
12 | hg init a |
|
12 | hg init a | |
13 | cd a |
|
13 | cd a | |
14 | echo a > a |
|
14 | echo a > a | |
15 | hg ci -d'0 0' -Ama |
|
15 | hg ci -d'0 0' -Ama | |
16 | hg cp a b |
|
16 | hg cp a b | |
17 | hg ci -d'1 0' -mb |
|
17 | hg ci -d'1 0' -mb | |
18 | hg rm a |
|
18 | hg rm a | |
19 | hg ci -d'2 0' -mc |
|
19 | hg ci -d'2 0' -mc | |
20 | hg mv b a |
|
20 | hg mv b a | |
21 | hg ci -d'3 0' -md |
|
21 | hg ci -d'3 0' -md | |
22 | echo a >> a |
|
22 | echo a >> a | |
23 | hg ci -d'4 0' -me |
|
23 | hg ci -d'4 0' -me | |
24 |
|
24 | |||
25 | cd .. |
|
25 | cd .. | |
26 | hg convert a 2>&1 | grep -v 'subversion python bindings could not be loaded' |
|
26 | hg convert a 2>&1 | grep -v 'subversion python bindings could not be loaded' | |
27 | hg --cwd a-hg pull ../a |
|
27 | hg --cwd a-hg pull ../a | |
28 |
|
28 | |||
29 | touch bogusfile |
|
29 | touch bogusfile | |
30 | echo % should fail |
|
30 | echo % should fail | |
31 | hg convert a bogusfile |
|
31 | hg convert a bogusfile | |
32 |
|
32 | |||
33 | mkdir bogusdir |
|
33 | mkdir bogusdir | |
34 | chmod 000 bogusdir |
|
34 | chmod 000 bogusdir | |
35 |
|
35 | |||
36 | echo % should fail |
|
36 | echo % should fail | |
37 | hg convert a bogusdir |
|
37 | hg convert a bogusdir | |
38 |
|
38 | |||
39 | echo % should succeed |
|
39 | echo % should succeed | |
40 | chmod 700 bogusdir |
|
40 | chmod 700 bogusdir | |
41 | hg convert a bogusdir |
|
41 | hg convert a bogusdir | |
42 |
|
42 | |||
43 | echo % test pre and post conversion actions |
|
43 | echo % test pre and post conversion actions | |
44 | echo 'include b' > filemap |
|
44 | echo 'include b' > filemap | |
45 | hg convert --debug --filemap filemap a partialb | \ |
|
45 | hg convert --debug --filemap filemap a partialb | \ | |
46 | grep 'run hg' |
|
46 | grep 'run hg' | |
|
47 | ||||
|
48 | echo % converting empty dir should fail "nicely" | |||
|
49 | mkdir emptydir | |||
|
50 | PATH=$BINDIR hg convert emptydir 2>&1 | sed 's,file://.*/emptydir,.../emptydir,g' |
@@ -1,228 +1,241 | |||||
1 | hg convert [OPTION]... SOURCE [DEST [REVMAP]] |
|
1 | hg convert [OPTION]... SOURCE [DEST [REVMAP]] | |
2 |
|
2 | |||
3 | convert a foreign SCM repository to a Mercurial one. |
|
3 | convert a foreign SCM repository to a Mercurial one. | |
4 |
|
4 | |||
5 | Accepted source formats [identifiers]: |
|
5 | Accepted source formats [identifiers]: | |
6 | - Mercurial [hg] |
|
6 | - Mercurial [hg] | |
7 | - CVS [cvs] |
|
7 | - CVS [cvs] | |
8 | - Darcs [darcs] |
|
8 | - Darcs [darcs] | |
9 | - git [git] |
|
9 | - git [git] | |
10 | - Subversion [svn] |
|
10 | - Subversion [svn] | |
11 | - Monotone [mtn] |
|
11 | - Monotone [mtn] | |
12 | - GNU Arch [gnuarch] |
|
12 | - GNU Arch [gnuarch] | |
13 | - Bazaar [bzr] |
|
13 | - Bazaar [bzr] | |
14 | - Perforce [p4] |
|
14 | - Perforce [p4] | |
15 |
|
15 | |||
16 | Accepted destination formats [identifiers]: |
|
16 | Accepted destination formats [identifiers]: | |
17 | - Mercurial [hg] |
|
17 | - Mercurial [hg] | |
18 | - Subversion [svn] (history on branches is not preserved) |
|
18 | - Subversion [svn] (history on branches is not preserved) | |
19 |
|
19 | |||
20 | If no revision is given, all revisions will be converted. Otherwise, |
|
20 | If no revision is given, all revisions will be converted. Otherwise, | |
21 | convert will only import up to the named revision (given in a format |
|
21 | convert will only import up to the named revision (given in a format | |
22 | understood by the source). |
|
22 | understood by the source). | |
23 |
|
23 | |||
24 | If no destination directory name is specified, it defaults to the |
|
24 | If no destination directory name is specified, it defaults to the | |
25 | basename of the source with '-hg' appended. If the destination |
|
25 | basename of the source with '-hg' appended. If the destination | |
26 | repository doesn't exist, it will be created. |
|
26 | repository doesn't exist, it will be created. | |
27 |
|
27 | |||
28 | If <REVMAP> isn't given, it will be put in a default location |
|
28 | If <REVMAP> isn't given, it will be put in a default location | |
29 | (<dest>/.hg/shamap by default). The <REVMAP> is a simple text |
|
29 | (<dest>/.hg/shamap by default). The <REVMAP> is a simple text | |
30 | file that maps each source commit ID to the destination ID for |
|
30 | file that maps each source commit ID to the destination ID for | |
31 | that revision, like so: |
|
31 | that revision, like so: | |
32 | <source ID> <destination ID> |
|
32 | <source ID> <destination ID> | |
33 |
|
33 | |||
34 | If the file doesn't exist, it's automatically created. It's updated |
|
34 | If the file doesn't exist, it's automatically created. It's updated | |
35 | on each commit copied, so convert-repo can be interrupted and can |
|
35 | on each commit copied, so convert-repo can be interrupted and can | |
36 | be run repeatedly to copy new commits. |
|
36 | be run repeatedly to copy new commits. | |
37 |
|
37 | |||
38 | The [username mapping] file is a simple text file that maps each source |
|
38 | The [username mapping] file is a simple text file that maps each source | |
39 | commit author to a destination commit author. It is handy for source SCMs |
|
39 | commit author to a destination commit author. It is handy for source SCMs | |
40 | that use unix logins to identify authors (eg: CVS). One line per author |
|
40 | that use unix logins to identify authors (eg: CVS). One line per author | |
41 | mapping and the line format is: |
|
41 | mapping and the line format is: | |
42 | srcauthor=whatever string you want |
|
42 | srcauthor=whatever string you want | |
43 |
|
43 | |||
44 | The filemap is a file that allows filtering and remapping of files |
|
44 | The filemap is a file that allows filtering and remapping of files | |
45 | and directories. Comment lines start with '#'. Each line can |
|
45 | and directories. Comment lines start with '#'. Each line can | |
46 | contain one of the following directives: |
|
46 | contain one of the following directives: | |
47 |
|
47 | |||
48 | include path/to/file |
|
48 | include path/to/file | |
49 |
|
49 | |||
50 | exclude path/to/file |
|
50 | exclude path/to/file | |
51 |
|
51 | |||
52 | rename from/file to/file |
|
52 | rename from/file to/file | |
53 |
|
53 | |||
54 | The 'include' directive causes a file, or all files under a |
|
54 | The 'include' directive causes a file, or all files under a | |
55 | directory, to be included in the destination repository, and the |
|
55 | directory, to be included in the destination repository, and the | |
56 | exclusion of all other files and dirs not explicitely included. |
|
56 | exclusion of all other files and dirs not explicitely included. | |
57 | The 'exclude' directive causes files or directories to be omitted. |
|
57 | The 'exclude' directive causes files or directories to be omitted. | |
58 | The 'rename' directive renames a file or directory. To rename from a |
|
58 | The 'rename' directive renames a file or directory. To rename from a | |
59 | subdirectory into the root of the repository, use '.' as the path to |
|
59 | subdirectory into the root of the repository, use '.' as the path to | |
60 | rename to. |
|
60 | rename to. | |
61 |
|
61 | |||
62 | The splicemap is a file that allows insertion of synthetic |
|
62 | The splicemap is a file that allows insertion of synthetic | |
63 | history, letting you specify the parents of a revision. This is |
|
63 | history, letting you specify the parents of a revision. This is | |
64 | useful if you want to e.g. give a Subversion merge two parents, or |
|
64 | useful if you want to e.g. give a Subversion merge two parents, or | |
65 | graft two disconnected series of history together. Each entry |
|
65 | graft two disconnected series of history together. Each entry | |
66 | contains a key, followed by a space, followed by one or two |
|
66 | contains a key, followed by a space, followed by one or two | |
67 | values, separated by spaces. The key is the revision ID in the |
|
67 | values, separated by spaces. The key is the revision ID in the | |
68 | source revision control system whose parents should be modified |
|
68 | source revision control system whose parents should be modified | |
69 | (same format as a key in .hg/shamap). The values are the revision |
|
69 | (same format as a key in .hg/shamap). The values are the revision | |
70 | IDs (in either the source or destination revision control system) |
|
70 | IDs (in either the source or destination revision control system) | |
71 | that should be used as the new parents for that node. |
|
71 | that should be used as the new parents for that node. | |
72 |
|
72 | |||
73 | Mercurial Source |
|
73 | Mercurial Source | |
74 | ----------------- |
|
74 | ----------------- | |
75 |
|
75 | |||
76 | --config convert.hg.ignoreerrors=False (boolean) |
|
76 | --config convert.hg.ignoreerrors=False (boolean) | |
77 | ignore integrity errors when reading. Use it to fix Mercurial |
|
77 | ignore integrity errors when reading. Use it to fix Mercurial | |
78 | repositories with missing revlogs, by converting from and to |
|
78 | repositories with missing revlogs, by converting from and to | |
79 | Mercurial. |
|
79 | Mercurial. | |
80 | --config convert.hg.saverev=False (boolean) |
|
80 | --config convert.hg.saverev=False (boolean) | |
81 | store original revision ID in changeset (forces target IDs to change) |
|
81 | store original revision ID in changeset (forces target IDs to change) | |
82 | --config convert.hg.startrev=0 (hg revision identifier) |
|
82 | --config convert.hg.startrev=0 (hg revision identifier) | |
83 | convert start revision and its descendants |
|
83 | convert start revision and its descendants | |
84 |
|
84 | |||
85 | CVS Source |
|
85 | CVS Source | |
86 | ---------- |
|
86 | ---------- | |
87 |
|
87 | |||
88 | CVS source will use a sandbox (i.e. a checked-out copy) from CVS |
|
88 | CVS source will use a sandbox (i.e. a checked-out copy) from CVS | |
89 | to indicate the starting point of what will be converted. Direct |
|
89 | to indicate the starting point of what will be converted. Direct | |
90 | access to the repository files is not needed, unless of course |
|
90 | access to the repository files is not needed, unless of course | |
91 | the repository is :local:. The conversion uses the top level |
|
91 | the repository is :local:. The conversion uses the top level | |
92 | directory in the sandbox to find the CVS repository, and then uses |
|
92 | directory in the sandbox to find the CVS repository, and then uses | |
93 | CVS rlog commands to find files to convert. This means that unless |
|
93 | CVS rlog commands to find files to convert. This means that unless | |
94 | a filemap is given, all files under the starting directory will be |
|
94 | a filemap is given, all files under the starting directory will be | |
95 | converted, and that any directory reorganisation in the CVS |
|
95 | converted, and that any directory reorganisation in the CVS | |
96 | sandbox is ignored. |
|
96 | sandbox is ignored. | |
97 |
|
97 | |||
98 | Because CVS does not have changesets, it is necessary to collect |
|
98 | Because CVS does not have changesets, it is necessary to collect | |
99 | individual commits to CVS and merge them into changesets. CVS |
|
99 | individual commits to CVS and merge them into changesets. CVS | |
100 | source uses its internal changeset merging code by default but can |
|
100 | source uses its internal changeset merging code by default but can | |
101 | be configured to call the external 'cvsps' program by setting: |
|
101 | be configured to call the external 'cvsps' program by setting: | |
102 | --config convert.cvsps='cvsps -A -u --cvs-direct -q' |
|
102 | --config convert.cvsps='cvsps -A -u --cvs-direct -q' | |
103 | This is a legacy option and may be removed in future. |
|
103 | This is a legacy option and may be removed in future. | |
104 |
|
104 | |||
105 | The options shown are the defaults. |
|
105 | The options shown are the defaults. | |
106 |
|
106 | |||
107 | Internal cvsps is selected by setting |
|
107 | Internal cvsps is selected by setting | |
108 | --config convert.cvsps=builtin |
|
108 | --config convert.cvsps=builtin | |
109 | and has a few more configurable options: |
|
109 | and has a few more configurable options: | |
110 | --config convert.cvsps.fuzz=60 (integer) |
|
110 | --config convert.cvsps.fuzz=60 (integer) | |
111 | Specify the maximum time (in seconds) that is allowed between |
|
111 | Specify the maximum time (in seconds) that is allowed between | |
112 | commits with identical user and log message in a single |
|
112 | commits with identical user and log message in a single | |
113 | changeset. When very large files were checked in as part |
|
113 | changeset. When very large files were checked in as part | |
114 | of a changeset then the default may not be long enough. |
|
114 | of a changeset then the default may not be long enough. | |
115 | --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}' |
|
115 | --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}' | |
116 | Specify a regular expression to which commit log messages are |
|
116 | Specify a regular expression to which commit log messages are | |
117 | matched. If a match occurs, then the conversion process will |
|
117 | matched. If a match occurs, then the conversion process will | |
118 | insert a dummy revision merging the branch on which this log |
|
118 | insert a dummy revision merging the branch on which this log | |
119 | message occurs to the branch indicated in the regex. |
|
119 | message occurs to the branch indicated in the regex. | |
120 | --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}' |
|
120 | --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}' | |
121 | Specify a regular expression to which commit log messages are |
|
121 | Specify a regular expression to which commit log messages are | |
122 | matched. If a match occurs, then the conversion process will |
|
122 | matched. If a match occurs, then the conversion process will | |
123 | add the most recent revision on the branch indicated in the |
|
123 | add the most recent revision on the branch indicated in the | |
124 | regex as the second parent of the changeset. |
|
124 | regex as the second parent of the changeset. | |
125 |
|
125 | |||
126 | The hgext/convert/cvsps wrapper script allows the builtin changeset |
|
126 | The hgext/convert/cvsps wrapper script allows the builtin changeset | |
127 | merging code to be run without doing a conversion. Its parameters and |
|
127 | merging code to be run without doing a conversion. Its parameters and | |
128 | output are similar to that of cvsps 2.1. |
|
128 | output are similar to that of cvsps 2.1. | |
129 |
|
129 | |||
130 | Subversion Source |
|
130 | Subversion Source | |
131 | ----------------- |
|
131 | ----------------- | |
132 |
|
132 | |||
133 | Subversion source detects classical trunk/branches/tags layouts. |
|
133 | Subversion source detects classical trunk/branches/tags layouts. | |
134 | By default, the supplied "svn://repo/path/" source URL is |
|
134 | By default, the supplied "svn://repo/path/" source URL is | |
135 | converted as a single branch. If "svn://repo/path/trunk" exists |
|
135 | converted as a single branch. If "svn://repo/path/trunk" exists | |
136 | it replaces the default branch. If "svn://repo/path/branches" |
|
136 | it replaces the default branch. If "svn://repo/path/branches" | |
137 | exists, its subdirectories are listed as possible branches. If |
|
137 | exists, its subdirectories are listed as possible branches. If | |
138 | "svn://repo/path/tags" exists, it is looked for tags referencing |
|
138 | "svn://repo/path/tags" exists, it is looked for tags referencing | |
139 | converted branches. Default "trunk", "branches" and "tags" values |
|
139 | converted branches. Default "trunk", "branches" and "tags" values | |
140 | can be overriden with following options. Set them to paths |
|
140 | can be overriden with following options. Set them to paths | |
141 | relative to the source URL, or leave them blank to disable |
|
141 | relative to the source URL, or leave them blank to disable | |
142 | autodetection. |
|
142 | autodetection. | |
143 |
|
143 | |||
144 | --config convert.svn.branches=branches (directory name) |
|
144 | --config convert.svn.branches=branches (directory name) | |
145 | specify the directory containing branches |
|
145 | specify the directory containing branches | |
146 | --config convert.svn.tags=tags (directory name) |
|
146 | --config convert.svn.tags=tags (directory name) | |
147 | specify the directory containing tags |
|
147 | specify the directory containing tags | |
148 | --config convert.svn.trunk=trunk (directory name) |
|
148 | --config convert.svn.trunk=trunk (directory name) | |
149 | specify the name of the trunk branch |
|
149 | specify the name of the trunk branch | |
150 |
|
150 | |||
151 | Source history can be retrieved starting at a specific revision, |
|
151 | Source history can be retrieved starting at a specific revision, | |
152 | instead of being integrally converted. Only single branch |
|
152 | instead of being integrally converted. Only single branch | |
153 | conversions are supported. |
|
153 | conversions are supported. | |
154 |
|
154 | |||
155 | --config convert.svn.startrev=0 (svn revision number) |
|
155 | --config convert.svn.startrev=0 (svn revision number) | |
156 | specify start Subversion revision. |
|
156 | specify start Subversion revision. | |
157 |
|
157 | |||
158 | Perforce Source |
|
158 | Perforce Source | |
159 | --------------- |
|
159 | --------------- | |
160 |
|
160 | |||
161 | The Perforce (P4) importer can be given a p4 depot path or a client |
|
161 | The Perforce (P4) importer can be given a p4 depot path or a client | |
162 | specification as source. It will convert all files in the source to |
|
162 | specification as source. It will convert all files in the source to | |
163 | a flat Mercurial repository, ignoring labels, branches and integrations. |
|
163 | a flat Mercurial repository, ignoring labels, branches and integrations. | |
164 | Note that when a depot path is given you then usually should specify a |
|
164 | Note that when a depot path is given you then usually should specify a | |
165 | target directory, because otherwise the target may be named ...-hg. |
|
165 | target directory, because otherwise the target may be named ...-hg. | |
166 |
|
166 | |||
167 | It is possible to limit the amount of source history to be converted |
|
167 | It is possible to limit the amount of source history to be converted | |
168 | by specifying an initial Perforce revision. |
|
168 | by specifying an initial Perforce revision. | |
169 |
|
169 | |||
170 | --config convert.p4.startrev=0 (perforce changelist number) |
|
170 | --config convert.p4.startrev=0 (perforce changelist number) | |
171 | specify initial Perforce revision. |
|
171 | specify initial Perforce revision. | |
172 |
|
172 | |||
173 |
|
173 | |||
174 | Mercurial Destination |
|
174 | Mercurial Destination | |
175 | --------------------- |
|
175 | --------------------- | |
176 |
|
176 | |||
177 | --config convert.hg.clonebranches=False (boolean) |
|
177 | --config convert.hg.clonebranches=False (boolean) | |
178 | dispatch source branches in separate clones. |
|
178 | dispatch source branches in separate clones. | |
179 | --config convert.hg.tagsbranch=default (branch name) |
|
179 | --config convert.hg.tagsbranch=default (branch name) | |
180 | tag revisions branch name |
|
180 | tag revisions branch name | |
181 | --config convert.hg.usebranchnames=True (boolean) |
|
181 | --config convert.hg.usebranchnames=True (boolean) | |
182 | preserve branch names |
|
182 | preserve branch names | |
183 |
|
183 | |||
184 | options: |
|
184 | options: | |
185 |
|
185 | |||
186 | -A --authors username mapping filename |
|
186 | -A --authors username mapping filename | |
187 | -d --dest-type destination repository type |
|
187 | -d --dest-type destination repository type | |
188 | --filemap remap file names using contents of file |
|
188 | --filemap remap file names using contents of file | |
189 | -r --rev import up to target revision REV |
|
189 | -r --rev import up to target revision REV | |
190 | -s --source-type source repository type |
|
190 | -s --source-type source repository type | |
191 | --splicemap splice synthesized history into place |
|
191 | --splicemap splice synthesized history into place | |
192 | --datesort try to sort changesets by date |
|
192 | --datesort try to sort changesets by date | |
193 |
|
193 | |||
194 | use "hg -v help convert" to show global options |
|
194 | use "hg -v help convert" to show global options | |
195 | adding a |
|
195 | adding a | |
196 | assuming destination a-hg |
|
196 | assuming destination a-hg | |
197 | initializing destination a-hg repository |
|
197 | initializing destination a-hg repository | |
198 | scanning source... |
|
198 | scanning source... | |
199 | sorting... |
|
199 | sorting... | |
200 | converting... |
|
200 | converting... | |
201 | 4 a |
|
201 | 4 a | |
202 | 3 b |
|
202 | 3 b | |
203 | 2 c |
|
203 | 2 c | |
204 | 1 d |
|
204 | 1 d | |
205 | 0 e |
|
205 | 0 e | |
206 | pulling from ../a |
|
206 | pulling from ../a | |
207 | searching for changes |
|
207 | searching for changes | |
208 | no changes found |
|
208 | no changes found | |
209 | % should fail |
|
209 | % should fail | |
210 | initializing destination bogusfile repository |
|
210 | initializing destination bogusfile repository | |
211 | abort: cannot create new bundle repository |
|
211 | abort: cannot create new bundle repository | |
212 | % should fail |
|
212 | % should fail | |
213 | abort: Permission denied: bogusdir |
|
213 | abort: Permission denied: bogusdir | |
214 | % should succeed |
|
214 | % should succeed | |
215 | initializing destination bogusdir repository |
|
215 | initializing destination bogusdir repository | |
216 | scanning source... |
|
216 | scanning source... | |
217 | sorting... |
|
217 | sorting... | |
218 | converting... |
|
218 | converting... | |
219 | 4 a |
|
219 | 4 a | |
220 | 3 b |
|
220 | 3 b | |
221 | 2 c |
|
221 | 2 c | |
222 | 1 d |
|
222 | 1 d | |
223 | 0 e |
|
223 | 0 e | |
224 | % test pre and post conversion actions |
|
224 | % test pre and post conversion actions | |
225 | run hg source pre-conversion action |
|
225 | run hg source pre-conversion action | |
226 | run hg sink pre-conversion action |
|
226 | run hg sink pre-conversion action | |
227 | run hg sink post-conversion action |
|
227 | run hg sink post-conversion action | |
228 | run hg source post-conversion action |
|
228 | run hg source post-conversion action | |
|
229 | % converting empty dir should fail nicely | |||
|
230 | assuming destination emptydir-hg | |||
|
231 | initializing destination emptydir-hg repository | |||
|
232 | emptydir does not look like a CVS checkout | |||
|
233 | emptydir does not look like a Git repo | |||
|
234 | .../emptydir does not look like a Subversion repo | |||
|
235 | emptydir is not a local Mercurial repo | |||
|
236 | emptydir does not look like a darcs repo | |||
|
237 | cannot find required "mtn" tool | |||
|
238 | emptydir does not look like a GNU Arch repo | |||
|
239 | emptydir does not look like a Bazaar repo | |||
|
240 | cannot find required "p4" tool | |||
|
241 | abort: emptydir: missing or unsupported repository |
General Comments 0
You need to be logged in to leave comments.
Login now