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