Show More
@@ -0,0 +1,176 b'' | |||||
|
1 | # | |||
|
2 | # Perforce source for convert extension. | |||
|
3 | # | |||
|
4 | # Copyright 2009, Frank Kingswood <frank@kingswood-consulting.co.uk> | |||
|
5 | # | |||
|
6 | # This software may be used and distributed according to the terms | |||
|
7 | # of the GNU General Public License, incorporated herein by reference. | |||
|
8 | # | |||
|
9 | ||||
|
10 | from mercurial import util | |||
|
11 | from mercurial.i18n import _ | |||
|
12 | ||||
|
13 | from common import commit, converter_source, checktool | |||
|
14 | import marshal | |||
|
15 | ||||
|
16 | def loaditer(f): | |||
|
17 | "Yield the dictionary objects generated by p4" | |||
|
18 | try: | |||
|
19 | while True: | |||
|
20 | d = marshal.load(f) | |||
|
21 | if not d: | |||
|
22 | break | |||
|
23 | yield d | |||
|
24 | except EOFError: | |||
|
25 | pass | |||
|
26 | ||||
|
27 | class p4_source(converter_source): | |||
|
28 | def __init__(self, ui, path, rev=None): | |||
|
29 | super(p4_source, self).__init__(ui, path, rev=rev) | |||
|
30 | ||||
|
31 | checktool('p4') | |||
|
32 | ||||
|
33 | self.p4changes = {} | |||
|
34 | self.heads = {} | |||
|
35 | self.changeset = {} | |||
|
36 | self.files = {} | |||
|
37 | self.tags = {} | |||
|
38 | self.lastbranch = {} | |||
|
39 | self.parent = {} | |||
|
40 | self.encoding = "latin_1" | |||
|
41 | self.depotname = {} # mapping from local name to depot name | |||
|
42 | self.modecache = {} | |||
|
43 | ||||
|
44 | self._parse(ui, path) | |||
|
45 | ||||
|
46 | def _parse_view(self, path): | |||
|
47 | "Read changes affecting the path" | |||
|
48 | cmd = "p4 -G changes -s submitted '%s'" % path | |||
|
49 | stdout = util.popen(cmd) | |||
|
50 | for d in loaditer(stdout): | |||
|
51 | c = d.get("change", None) | |||
|
52 | if c: | |||
|
53 | self.p4changes[c] = True | |||
|
54 | ||||
|
55 | def _parse(self, ui, path): | |||
|
56 | "Prepare list of P4 filenames and revisions to import" | |||
|
57 | ui.status(_('reading p4 views\n')) | |||
|
58 | ||||
|
59 | # read client spec or view | |||
|
60 | if "/" in path: | |||
|
61 | self._parse_view(path) | |||
|
62 | if path.startswith("//") and path.endswith("/..."): | |||
|
63 | views = {path[:-3]:""} | |||
|
64 | else: | |||
|
65 | views = {"//": ""} | |||
|
66 | else: | |||
|
67 | cmd = "p4 -G client -o '%s'" % path | |||
|
68 | clientspec = marshal.load(util.popen(cmd)) | |||
|
69 | ||||
|
70 | views = {} | |||
|
71 | for client in clientspec: | |||
|
72 | if client.startswith("View"): | |||
|
73 | sview, cview = clientspec[client].split() | |||
|
74 | self._parse_view(sview) | |||
|
75 | if sview.endswith("...") and cview.endswith("..."): | |||
|
76 | sview = sview[:-3] | |||
|
77 | cview = cview[:-3] | |||
|
78 | cview = cview[2:] | |||
|
79 | cview = cview[cview.find("/") + 1:] | |||
|
80 | views[sview] = cview | |||
|
81 | ||||
|
82 | # list of changes that affect our source files | |||
|
83 | self.p4changes = self.p4changes.keys() | |||
|
84 | self.p4changes.sort(key=int) | |||
|
85 | ||||
|
86 | # list with depot pathnames, longest first | |||
|
87 | vieworder = views.keys() | |||
|
88 | vieworder.sort(key=lambda x: -len(x)) | |||
|
89 | ||||
|
90 | # handle revision limiting | |||
|
91 | startrev = self.ui.config('convert', 'p4.startrev', default=0) | |||
|
92 | self.p4changes = [x for x in self.p4changes | |||
|
93 | if ((not startrev or int(x) >= int(startrev)) and | |||
|
94 | (not self.rev or int(x) <= int(self.rev)))] | |||
|
95 | ||||
|
96 | # now read the full changelists to get the list of file revisions | |||
|
97 | ui.status(_('collecting p4 changelists\n')) | |||
|
98 | lastid = None | |||
|
99 | for change in self.p4changes: | |||
|
100 | cmd = "p4 -G describe %s" % change | |||
|
101 | stdout = util.popen(cmd) | |||
|
102 | d = marshal.load(stdout) | |||
|
103 | ||||
|
104 | desc = self.recode(d["desc"]) | |||
|
105 | shortdesc = desc.split("\n", 1)[0] | |||
|
106 | t = '%s %s' % (d["change"], repr(shortdesc)[1:-1]) | |||
|
107 | ui.status(util.ellipsis(t, 80) + '\n') | |||
|
108 | ||||
|
109 | if lastid: | |||
|
110 | parents = [lastid] | |||
|
111 | else: | |||
|
112 | parents = [] | |||
|
113 | ||||
|
114 | date = (int(d["time"]), 0) # timezone not set | |||
|
115 | c = commit(author=self.recode(d["user"]), date=util.datestr(date), | |||
|
116 | parents=parents, desc=desc, branch='', extra={"p4": change}) | |||
|
117 | ||||
|
118 | files = [] | |||
|
119 | i = 0 | |||
|
120 | while ("depotFile%d" % i) in d and ("rev%d" % i) in d: | |||
|
121 | oldname = d["depotFile%d" % i] | |||
|
122 | filename = None | |||
|
123 | for v in vieworder: | |||
|
124 | if oldname.startswith(v): | |||
|
125 | filename = views[v] + oldname[len(v):] | |||
|
126 | break | |||
|
127 | if filename: | |||
|
128 | files.append((filename, d["rev%d" % i])) | |||
|
129 | self.depotname[filename] = oldname | |||
|
130 | i += 1 | |||
|
131 | self.changeset[change] = c | |||
|
132 | self.files[change] = files | |||
|
133 | lastid = change | |||
|
134 | ||||
|
135 | if lastid: | |||
|
136 | self.heads = [lastid] | |||
|
137 | ||||
|
138 | def getheads(self): | |||
|
139 | return self.heads | |||
|
140 | ||||
|
141 | def getfile(self, name, rev): | |||
|
142 | cmd = "p4 -G print '%s#%s'" % (self.depotname[name], rev) | |||
|
143 | stdout = util.popen(cmd) | |||
|
144 | ||||
|
145 | mode = None | |||
|
146 | data = "" | |||
|
147 | ||||
|
148 | for d in loaditer(stdout): | |||
|
149 | if d["code"] == "stat": | |||
|
150 | if "+x" in d["type"]: | |||
|
151 | mode = "x" | |||
|
152 | else: | |||
|
153 | mode = "" | |||
|
154 | elif d["code"] == "text": | |||
|
155 | data += d["data"] | |||
|
156 | ||||
|
157 | if mode is None: | |||
|
158 | raise IOError() | |||
|
159 | ||||
|
160 | self.modecache[(name, rev)] = mode | |||
|
161 | return data | |||
|
162 | ||||
|
163 | def getmode(self, name, rev): | |||
|
164 | return self.modecache[(name, rev)] | |||
|
165 | ||||
|
166 | def getchanges(self, rev): | |||
|
167 | return self.files[rev], {} | |||
|
168 | ||||
|
169 | def getcommit(self, rev): | |||
|
170 | return self.changeset[rev] | |||
|
171 | ||||
|
172 | def gettags(self): | |||
|
173 | return self.tags | |||
|
174 | ||||
|
175 | def getchangedfiles(self, rev, i): | |||
|
176 | return util.sort([x[0] for x in self.files[rev]]) |
@@ -0,0 +1,75 b'' | |||||
|
1 | #!/bin/sh | |||
|
2 | ||||
|
3 | "$TESTDIR/hghave" p4 || exit 80 | |||
|
4 | ||||
|
5 | echo "[extensions]" >> $HGRCPATH | |||
|
6 | echo "convert = " >> $HGRCPATH | |||
|
7 | ||||
|
8 | echo % create p4 depot | |||
|
9 | export P4ROOT=$PWD/depot | |||
|
10 | export P4AUDIT=$P4ROOT/audit | |||
|
11 | export P4JOURNAL=$P4ROOT/journal | |||
|
12 | export P4LOG=$P4ROOT/log | |||
|
13 | export P4PORT=localhost:16661 | |||
|
14 | export P4DEBUG=1 | |||
|
15 | ||||
|
16 | echo % start the p4 server | |||
|
17 | [ ! -d $P4ROOT ] && mkdir $P4ROOT | |||
|
18 | p4d -f -J off >$P4ROOT/stdout 2>$P4ROOT/stderr & | |||
|
19 | trap "echo % stop the p4 server ; p4 admin stop" EXIT | |||
|
20 | ||||
|
21 | # wait for the server to initialize | |||
|
22 | while ! p4 ; do | |||
|
23 | sleep 1 | |||
|
24 | done >/dev/null 2>/dev/null | |||
|
25 | ||||
|
26 | echo % create a client spec | |||
|
27 | export P4CLIENT=hg-p4-import | |||
|
28 | DEPOTPATH=//depot/test-mercurial-import/... | |||
|
29 | p4 client -o | sed '/^View:/,$ d' >p4client | |||
|
30 | echo View: >>p4client | |||
|
31 | echo " $DEPOTPATH //$P4CLIENT/..." >>p4client | |||
|
32 | p4 client -i <p4client | |||
|
33 | ||||
|
34 | echo % populate the depot | |||
|
35 | echo a > a | |||
|
36 | mkdir b | |||
|
37 | echo c > b/c | |||
|
38 | p4 add a b/c | |||
|
39 | p4 submit -d initial | |||
|
40 | ||||
|
41 | echo % change some files | |||
|
42 | p4 edit a | |||
|
43 | echo aa >> a | |||
|
44 | p4 submit -d "change a" | |||
|
45 | ||||
|
46 | p4 edit b/c | |||
|
47 | echo cc >> b/c | |||
|
48 | p4 submit -d "change b/c" | |||
|
49 | ||||
|
50 | echo % convert | |||
|
51 | hg convert -s p4 $DEPOTPATH dst | |||
|
52 | hg -R dst log --template 'rev=#rev# desc="#desc#" tags="#tags#" files="#files#"\n' | |||
|
53 | ||||
|
54 | echo % change some files | |||
|
55 | p4 edit a b/c | |||
|
56 | echo aaa >> a | |||
|
57 | echo ccc >> b/c | |||
|
58 | p4 submit -d "change a b/c" | |||
|
59 | ||||
|
60 | echo % convert again | |||
|
61 | hg convert -s p4 $DEPOTPATH dst | |||
|
62 | hg -R dst log --template 'rev=#rev# desc="#desc#" tags="#tags#" files="#files#"\n' | |||
|
63 | ||||
|
64 | echo % interesting names | |||
|
65 | echo dddd > "d d" | |||
|
66 | mkdir " e " | |||
|
67 | echo fff >" e /f " | |||
|
68 | p4 add "d d" " e /f " | |||
|
69 | p4 submit -d "add d e f" | |||
|
70 | ||||
|
71 | echo % convert again | |||
|
72 | hg convert -s p4 $DEPOTPATH dst | |||
|
73 | hg -R dst log --template 'rev=#rev# desc="#desc#" tags="#tags#" files="#files#"\n' | |||
|
74 | ||||
|
75 |
@@ -0,0 +1,88 b'' | |||||
|
1 | % create p4 depot | |||
|
2 | % start the p4 server | |||
|
3 | % create a client spec | |||
|
4 | Client hg-p4-import saved. | |||
|
5 | % populate the depot | |||
|
6 | //depot/test-mercurial-import/a#1 - opened for add | |||
|
7 | //depot/test-mercurial-import/b/c#1 - opened for add | |||
|
8 | Submitting change 1. | |||
|
9 | Locking 2 files ... | |||
|
10 | add //depot/test-mercurial-import/a#1 | |||
|
11 | add //depot/test-mercurial-import/b/c#1 | |||
|
12 | Change 1 submitted. | |||
|
13 | % change some files | |||
|
14 | //depot/test-mercurial-import/a#1 - opened for edit | |||
|
15 | Submitting change 2. | |||
|
16 | Locking 1 files ... | |||
|
17 | edit //depot/test-mercurial-import/a#2 | |||
|
18 | Change 2 submitted. | |||
|
19 | //depot/test-mercurial-import/b/c#1 - opened for edit | |||
|
20 | Submitting change 3. | |||
|
21 | Locking 1 files ... | |||
|
22 | edit //depot/test-mercurial-import/b/c#2 | |||
|
23 | Change 3 submitted. | |||
|
24 | % convert | |||
|
25 | initializing destination dst repository | |||
|
26 | reading p4 views | |||
|
27 | collecting p4 changelists | |||
|
28 | 1 initial | |||
|
29 | 2 change a | |||
|
30 | 3 change b/c | |||
|
31 | scanning source... | |||
|
32 | sorting... | |||
|
33 | converting... | |||
|
34 | 2 initial | |||
|
35 | 1 change a | |||
|
36 | 0 change b/c | |||
|
37 | rev=2 desc="change b/c" tags="tip" files="b/c" | |||
|
38 | rev=1 desc="change a" tags="" files="a" | |||
|
39 | rev=0 desc="initial" tags="" files="a b/c" | |||
|
40 | % change some files | |||
|
41 | //depot/test-mercurial-import/a#2 - opened for edit | |||
|
42 | //depot/test-mercurial-import/b/c#2 - opened for edit | |||
|
43 | Submitting change 4. | |||
|
44 | Locking 2 files ... | |||
|
45 | edit //depot/test-mercurial-import/a#3 | |||
|
46 | edit //depot/test-mercurial-import/b/c#3 | |||
|
47 | Change 4 submitted. | |||
|
48 | % convert again | |||
|
49 | reading p4 views | |||
|
50 | collecting p4 changelists | |||
|
51 | 1 initial | |||
|
52 | 2 change a | |||
|
53 | 3 change b/c | |||
|
54 | 4 change a b/c | |||
|
55 | scanning source... | |||
|
56 | sorting... | |||
|
57 | converting... | |||
|
58 | 0 change a b/c | |||
|
59 | rev=3 desc="change a b/c" tags="tip" files="a b/c" | |||
|
60 | rev=2 desc="change b/c" tags="" files="b/c" | |||
|
61 | rev=1 desc="change a" tags="" files="a" | |||
|
62 | rev=0 desc="initial" tags="" files="a b/c" | |||
|
63 | % interesting names | |||
|
64 | //depot/test-mercurial-import/d d#1 - opened for add | |||
|
65 | //depot/test-mercurial-import/ e /f #1 - opened for add | |||
|
66 | Submitting change 5. | |||
|
67 | Locking 2 files ... | |||
|
68 | add //depot/test-mercurial-import/ e /f #1 | |||
|
69 | add //depot/test-mercurial-import/d d#1 | |||
|
70 | Change 5 submitted. | |||
|
71 | % convert again | |||
|
72 | reading p4 views | |||
|
73 | collecting p4 changelists | |||
|
74 | 1 initial | |||
|
75 | 2 change a | |||
|
76 | 3 change b/c | |||
|
77 | 4 change a b/c | |||
|
78 | 5 add d e f | |||
|
79 | scanning source... | |||
|
80 | sorting... | |||
|
81 | converting... | |||
|
82 | 0 add d e f | |||
|
83 | rev=4 desc="add d e f" tags="tip" files=" e /f d d" | |||
|
84 | rev=3 desc="change a b/c" tags="" files="a b/c" | |||
|
85 | rev=2 desc="change b/c" tags="" files="b/c" | |||
|
86 | rev=1 desc="change a" tags="" files="a" | |||
|
87 | rev=0 desc="initial" tags="" files="a b/c" | |||
|
88 | % stop the p4 server |
@@ -25,6 +25,7 b' def convert(ui, src, dest=None, revmapfi' | |||||
25 | - Monotone [mtn] |
|
25 | - Monotone [mtn] | |
26 | - GNU Arch [gnuarch] |
|
26 | - GNU Arch [gnuarch] | |
27 | - Bazaar [bzr] |
|
27 | - Bazaar [bzr] | |
|
28 | - Perforce [p4] | |||
28 |
|
29 | |||
29 | Accepted destination formats [identifiers]: |
|
30 | Accepted destination formats [identifiers]: | |
30 | - Mercurial [hg] |
|
31 | - Mercurial [hg] | |
@@ -168,6 +169,22 b' def convert(ui, src, dest=None, revmapfi' | |||||
168 | --config convert.svn.startrev=0 (svn revision number) |
|
169 | --config convert.svn.startrev=0 (svn revision number) | |
169 | specify start Subversion revision. |
|
170 | specify start Subversion revision. | |
170 |
|
171 | |||
|
172 | Perforce Source | |||
|
173 | --------------- | |||
|
174 | ||||
|
175 | The Perforce (P4) importer can be given a p4 depot path or a client | |||
|
176 | specification as source. It will convert all files in the source to | |||
|
177 | a flat Mercurial repository, ignoring labels, branches and integrations. | |||
|
178 | Note that when a depot path is given you then usually should specify a | |||
|
179 | target directory, because otherwise the target may be named ...-hg. | |||
|
180 | ||||
|
181 | It is possible to limit the amount of source history to be converted | |||
|
182 | by specifying an initial Perforce revision. | |||
|
183 | ||||
|
184 | --config convert.p4.startrev=0 (perforce changelist number) | |||
|
185 | specify initial Perforce revision. | |||
|
186 | ||||
|
187 | ||||
171 | Mercurial Destination |
|
188 | Mercurial Destination | |
172 | --------------------- |
|
189 | --------------------- | |
173 |
|
190 |
@@ -14,6 +14,7 b' from subversion import debugsvnlog, svn_' | |||||
14 | from monotone import monotone_source |
|
14 | from monotone import monotone_source | |
15 | from gnuarch import gnuarch_source |
|
15 | from gnuarch import gnuarch_source | |
16 | from bzr import bzr_source |
|
16 | from bzr import bzr_source | |
|
17 | from p4 import p4_source | |||
17 | import filemap |
|
18 | import filemap | |
18 |
|
19 | |||
19 | import os, shutil |
|
20 | import os, shutil | |
@@ -37,6 +38,7 b' source_converters = [' | |||||
37 | ('mtn', monotone_source), |
|
38 | ('mtn', monotone_source), | |
38 | ('gnuarch', gnuarch_source), |
|
39 | ('gnuarch', gnuarch_source), | |
39 | ('bzr', bzr_source), |
|
40 | ('bzr', bzr_source), | |
|
41 | ('p4', p4_source), | |||
40 | ] |
|
42 | ] | |
41 |
|
43 | |||
42 | sink_converters = [ |
|
44 | sink_converters = [ |
@@ -125,6 +125,9 b' def has_svn_bindings():' | |||||
125 | except ImportError: |
|
125 | except ImportError: | |
126 | return False |
|
126 | return False | |
127 |
|
127 | |||
|
128 | def has_p4(): | |||
|
129 | return matchoutput('p4 -V', r'Rev\. P4/') and matchoutput('p4d -V', r'Rev\. P4D/') | |||
|
130 | ||||
128 | def has_symlink(): |
|
131 | def has_symlink(): | |
129 | return hasattr(os, "symlink") |
|
132 | return hasattr(os, "symlink") | |
130 |
|
133 | |||
@@ -173,6 +176,7 b' checks = {' | |||||
173 | "lsprof": (has_lsprof, "python lsprof module"), |
|
176 | "lsprof": (has_lsprof, "python lsprof module"), | |
174 | "mtn": (has_mtn, "monotone client (> 0.31)"), |
|
177 | "mtn": (has_mtn, "monotone client (> 0.31)"), | |
175 | "outer-repo": (has_outer_repo, "outer repo"), |
|
178 | "outer-repo": (has_outer_repo, "outer repo"), | |
|
179 | "p4": (has_p4, "Perforce server and client"), | |||
176 | "pygments": (has_pygments, "Pygments source highlighting library"), |
|
180 | "pygments": (has_pygments, "Pygments source highlighting library"), | |
177 | "svn": (has_svn, "subversion client and admin tools"), |
|
181 | "svn": (has_svn, "subversion client and admin tools"), | |
178 | "svn-bindings": (has_svn_bindings, "subversion python bindings"), |
|
182 | "svn-bindings": (has_svn_bindings, "subversion python bindings"), |
@@ -11,6 +11,7 b' convert a foreign SCM repository to a Me' | |||||
11 | - Monotone [mtn] |
|
11 | - Monotone [mtn] | |
12 | - GNU Arch [gnuarch] |
|
12 | - GNU Arch [gnuarch] | |
13 | - Bazaar [bzr] |
|
13 | - Bazaar [bzr] | |
|
14 | - Perforce [p4] | |||
14 |
|
15 | |||
15 | Accepted destination formats [identifiers]: |
|
16 | Accepted destination formats [identifiers]: | |
16 | - Mercurial [hg] |
|
17 | - Mercurial [hg] | |
@@ -154,6 +155,22 b' convert a foreign SCM repository to a Me' | |||||
154 | --config convert.svn.startrev=0 (svn revision number) |
|
155 | --config convert.svn.startrev=0 (svn revision number) | |
155 | specify start Subversion revision. |
|
156 | specify start Subversion revision. | |
156 |
|
157 | |||
|
158 | Perforce Source | |||
|
159 | --------------- | |||
|
160 | ||||
|
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 | |||
|
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 | |||
|
165 | target directory, because otherwise the target may be named ...-hg. | |||
|
166 | ||||
|
167 | It is possible to limit the amount of source history to be converted | |||
|
168 | by specifying an initial Perforce revision. | |||
|
169 | ||||
|
170 | --config convert.p4.startrev=0 (perforce changelist number) | |||
|
171 | specify initial Perforce revision. | |||
|
172 | ||||
|
173 | ||||
157 | Mercurial Destination |
|
174 | Mercurial Destination | |
158 | --------------------- |
|
175 | --------------------- | |
159 |
|
176 |
General Comments 0
You need to be logged in to leave comments.
Login now