##// END OF EJS Templates
contrib/synthrepo: only generate 2 parents if model contains merges...
Mike Edgar -
r22472:2e2577b0 default
parent child Browse files
Show More
@@ -1,376 +1,377
1 # synthrepo.py - repo synthesis
1 # synthrepo.py - repo synthesis
2 #
2 #
3 # Copyright 2012 Facebook
3 # Copyright 2012 Facebook
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''synthesize structurally interesting change history
8 '''synthesize structurally interesting change history
9
9
10 This extension is useful for creating a repository with properties
10 This extension is useful for creating a repository with properties
11 that are statistically similar to an existing repository. During
11 that are statistically similar to an existing repository. During
12 analysis, a simple probability table is constructed from the history
12 analysis, a simple probability table is constructed from the history
13 of an existing repository. During synthesis, these properties are
13 of an existing repository. During synthesis, these properties are
14 reconstructed.
14 reconstructed.
15
15
16 Properties that are analyzed and synthesized include the following:
16 Properties that are analyzed and synthesized include the following:
17
17
18 - Lines added or removed when an existing file is modified
18 - Lines added or removed when an existing file is modified
19 - Number and sizes of files added
19 - Number and sizes of files added
20 - Number of files removed
20 - Number of files removed
21 - Line lengths
21 - Line lengths
22 - Topological distance to parent changeset(s)
22 - Topological distance to parent changeset(s)
23 - Probability of a commit being a merge
23 - Probability of a commit being a merge
24 - Probability of a newly added file being added to a new directory
24 - Probability of a newly added file being added to a new directory
25 - Interarrival time, and time zone, of commits
25 - Interarrival time, and time zone, of commits
26
26
27 A few obvious properties that are not currently handled realistically:
27 A few obvious properties that are not currently handled realistically:
28
28
29 - Merges are treated as regular commits with two parents, which is not
29 - Merges are treated as regular commits with two parents, which is not
30 realistic
30 realistic
31 - Modifications are not treated as operations on hunks of lines, but
31 - Modifications are not treated as operations on hunks of lines, but
32 as insertions and deletions of randomly chosen single lines
32 as insertions and deletions of randomly chosen single lines
33 - Committer ID (always random)
33 - Committer ID (always random)
34 - Executability of files
34 - Executability of files
35 - Symlinks and binary files are ignored
35 - Symlinks and binary files are ignored
36 '''
36 '''
37
37
38 import bisect, collections, json, os, random, time, sys
38 import bisect, collections, json, os, random, time, sys
39 from mercurial import cmdutil, context, patch, scmutil, util, hg
39 from mercurial import cmdutil, context, patch, scmutil, util, hg
40 from mercurial.i18n import _
40 from mercurial.i18n import _
41 from mercurial.node import nullrev, nullid
41 from mercurial.node import nullrev, nullid
42
42
43 testedwith = 'internal'
43 testedwith = 'internal'
44
44
45 cmdtable = {}
45 cmdtable = {}
46 command = cmdutil.command(cmdtable)
46 command = cmdutil.command(cmdtable)
47
47
48 newfile = set(('new fi', 'rename', 'copy f', 'copy t'))
48 newfile = set(('new fi', 'rename', 'copy f', 'copy t'))
49
49
50 def zerodict():
50 def zerodict():
51 return collections.defaultdict(lambda: 0)
51 return collections.defaultdict(lambda: 0)
52
52
53 def roundto(x, k):
53 def roundto(x, k):
54 if x > k * 2:
54 if x > k * 2:
55 return int(round(x / float(k)) * k)
55 return int(round(x / float(k)) * k)
56 return int(round(x))
56 return int(round(x))
57
57
58 def parsegitdiff(lines):
58 def parsegitdiff(lines):
59 filename, mar, lineadd, lineremove = None, None, zerodict(), 0
59 filename, mar, lineadd, lineremove = None, None, zerodict(), 0
60 binary = False
60 binary = False
61 for line in lines:
61 for line in lines:
62 start = line[:6]
62 start = line[:6]
63 if start == 'diff -':
63 if start == 'diff -':
64 if filename:
64 if filename:
65 yield filename, mar, lineadd, lineremove, binary
65 yield filename, mar, lineadd, lineremove, binary
66 mar, lineadd, lineremove, binary = 'm', zerodict(), 0, False
66 mar, lineadd, lineremove, binary = 'm', zerodict(), 0, False
67 filename = patch.gitre.match(line).group(1)
67 filename = patch.gitre.match(line).group(1)
68 elif start in newfile:
68 elif start in newfile:
69 mar = 'a'
69 mar = 'a'
70 elif start == 'GIT bi':
70 elif start == 'GIT bi':
71 binary = True
71 binary = True
72 elif start == 'delete':
72 elif start == 'delete':
73 mar = 'r'
73 mar = 'r'
74 elif start:
74 elif start:
75 s = start[0]
75 s = start[0]
76 if s == '-' and not line.startswith('--- '):
76 if s == '-' and not line.startswith('--- '):
77 lineremove += 1
77 lineremove += 1
78 elif s == '+' and not line.startswith('+++ '):
78 elif s == '+' and not line.startswith('+++ '):
79 lineadd[roundto(len(line) - 1, 5)] += 1
79 lineadd[roundto(len(line) - 1, 5)] += 1
80 if filename:
80 if filename:
81 yield filename, mar, lineadd, lineremove, binary
81 yield filename, mar, lineadd, lineremove, binary
82
82
83 @command('analyze',
83 @command('analyze',
84 [('o', 'output', [], _('write output to given file'), _('FILE')),
84 [('o', 'output', [], _('write output to given file'), _('FILE')),
85 ('r', 'rev', [], _('analyze specified revisions'), _('REV'))],
85 ('r', 'rev', [], _('analyze specified revisions'), _('REV'))],
86 _('hg analyze'))
86 _('hg analyze'))
87 def analyze(ui, repo, *revs, **opts):
87 def analyze(ui, repo, *revs, **opts):
88 '''create a simple model of a repository to use for later synthesis
88 '''create a simple model of a repository to use for later synthesis
89
89
90 This command examines every changeset in the given range (or all
90 This command examines every changeset in the given range (or all
91 of history if none are specified) and creates a simple statistical
91 of history if none are specified) and creates a simple statistical
92 model of the history of the repository.
92 model of the history of the repository.
93
93
94 The model is written out to a JSON file, and can be used by
94 The model is written out to a JSON file, and can be used by
95 :hg:`synthesize` to create or augment a repository with synthetic
95 :hg:`synthesize` to create or augment a repository with synthetic
96 commits that have a structure that is statistically similar to the
96 commits that have a structure that is statistically similar to the
97 analyzed repository.
97 analyzed repository.
98 '''
98 '''
99
99
100 revs = list(revs)
100 revs = list(revs)
101 revs.extend(opts['rev'])
101 revs.extend(opts['rev'])
102 if not revs:
102 if not revs:
103 revs = [':']
103 revs = [':']
104
104
105 output = opts['output']
105 output = opts['output']
106 if not output:
106 if not output:
107 output = os.path.basename(repo.root) + '.json'
107 output = os.path.basename(repo.root) + '.json'
108
108
109 if output == '-':
109 if output == '-':
110 fp = sys.stdout
110 fp = sys.stdout
111 else:
111 else:
112 fp = open(output, 'w')
112 fp = open(output, 'w')
113
113
114 revs = scmutil.revrange(repo, revs)
114 revs = scmutil.revrange(repo, revs)
115 revs.sort()
115 revs.sort()
116
116
117 lineschanged = zerodict()
117 lineschanged = zerodict()
118 children = zerodict()
118 children = zerodict()
119 p1distance = zerodict()
119 p1distance = zerodict()
120 p2distance = zerodict()
120 p2distance = zerodict()
121 linesinfilesadded = zerodict()
121 linesinfilesadded = zerodict()
122 fileschanged = zerodict()
122 fileschanged = zerodict()
123 filesadded = zerodict()
123 filesadded = zerodict()
124 filesremoved = zerodict()
124 filesremoved = zerodict()
125 linelengths = zerodict()
125 linelengths = zerodict()
126 interarrival = zerodict()
126 interarrival = zerodict()
127 parents = zerodict()
127 parents = zerodict()
128 dirsadded = zerodict()
128 dirsadded = zerodict()
129 tzoffset = zerodict()
129 tzoffset = zerodict()
130
130
131 progress = ui.progress
131 progress = ui.progress
132 _analyzing = _('analyzing')
132 _analyzing = _('analyzing')
133 _changesets = _('changesets')
133 _changesets = _('changesets')
134 _total = len(revs)
134 _total = len(revs)
135
135
136 for i, rev in enumerate(revs):
136 for i, rev in enumerate(revs):
137 progress(_analyzing, i, unit=_changesets, total=_total)
137 progress(_analyzing, i, unit=_changesets, total=_total)
138 ctx = repo[rev]
138 ctx = repo[rev]
139 pl = ctx.parents()
139 pl = ctx.parents()
140 pctx = pl[0]
140 pctx = pl[0]
141 prev = pctx.rev()
141 prev = pctx.rev()
142 children[prev] += 1
142 children[prev] += 1
143 p1distance[rev - prev] += 1
143 p1distance[rev - prev] += 1
144 parents[len(pl)] += 1
144 parents[len(pl)] += 1
145 tzoffset[ctx.date()[1]] += 1
145 tzoffset[ctx.date()[1]] += 1
146 if len(pl) > 1:
146 if len(pl) > 1:
147 p2distance[rev - pl[1].rev()] += 1
147 p2distance[rev - pl[1].rev()] += 1
148 if prev == rev - 1:
148 if prev == rev - 1:
149 lastctx = pctx
149 lastctx = pctx
150 else:
150 else:
151 lastctx = repo[rev - 1]
151 lastctx = repo[rev - 1]
152 if lastctx.rev() != nullrev:
152 if lastctx.rev() != nullrev:
153 interarrival[roundto(ctx.date()[0] - lastctx.date()[0], 300)] += 1
153 interarrival[roundto(ctx.date()[0] - lastctx.date()[0], 300)] += 1
154 diff = sum((d.splitlines()
154 diff = sum((d.splitlines()
155 for d in ctx.diff(pctx, opts={'git': True})), [])
155 for d in ctx.diff(pctx, opts={'git': True})), [])
156 fileadds, diradds, fileremoves, filechanges = 0, 0, 0, 0
156 fileadds, diradds, fileremoves, filechanges = 0, 0, 0, 0
157 for filename, mar, lineadd, lineremove, binary in parsegitdiff(diff):
157 for filename, mar, lineadd, lineremove, binary in parsegitdiff(diff):
158 if binary:
158 if binary:
159 continue
159 continue
160 added = sum(lineadd.itervalues(), 0)
160 added = sum(lineadd.itervalues(), 0)
161 if mar == 'm':
161 if mar == 'm':
162 if added and lineremove:
162 if added and lineremove:
163 lineschanged[roundto(added, 5), roundto(lineremove, 5)] += 1
163 lineschanged[roundto(added, 5), roundto(lineremove, 5)] += 1
164 filechanges += 1
164 filechanges += 1
165 elif mar == 'a':
165 elif mar == 'a':
166 fileadds += 1
166 fileadds += 1
167 if '/' in filename:
167 if '/' in filename:
168 filedir = filename.rsplit('/', 1)[0]
168 filedir = filename.rsplit('/', 1)[0]
169 if filedir not in pctx.dirs():
169 if filedir not in pctx.dirs():
170 diradds += 1
170 diradds += 1
171 linesinfilesadded[roundto(added, 5)] += 1
171 linesinfilesadded[roundto(added, 5)] += 1
172 elif mar == 'r':
172 elif mar == 'r':
173 fileremoves += 1
173 fileremoves += 1
174 for length, count in lineadd.iteritems():
174 for length, count in lineadd.iteritems():
175 linelengths[length] += count
175 linelengths[length] += count
176 fileschanged[filechanges] += 1
176 fileschanged[filechanges] += 1
177 filesadded[fileadds] += 1
177 filesadded[fileadds] += 1
178 dirsadded[diradds] += 1
178 dirsadded[diradds] += 1
179 filesremoved[fileremoves] += 1
179 filesremoved[fileremoves] += 1
180
180
181 invchildren = zerodict()
181 invchildren = zerodict()
182
182
183 for rev, count in children.iteritems():
183 for rev, count in children.iteritems():
184 invchildren[count] += 1
184 invchildren[count] += 1
185
185
186 if output != '-':
186 if output != '-':
187 ui.status(_('writing output to %s\n') % output)
187 ui.status(_('writing output to %s\n') % output)
188
188
189 def pronk(d):
189 def pronk(d):
190 return sorted(d.iteritems(), key=lambda x: x[1], reverse=True)
190 return sorted(d.iteritems(), key=lambda x: x[1], reverse=True)
191
191
192 json.dump({'revs': len(revs),
192 json.dump({'revs': len(revs),
193 'lineschanged': pronk(lineschanged),
193 'lineschanged': pronk(lineschanged),
194 'children': pronk(invchildren),
194 'children': pronk(invchildren),
195 'fileschanged': pronk(fileschanged),
195 'fileschanged': pronk(fileschanged),
196 'filesadded': pronk(filesadded),
196 'filesadded': pronk(filesadded),
197 'linesinfilesadded': pronk(linesinfilesadded),
197 'linesinfilesadded': pronk(linesinfilesadded),
198 'dirsadded': pronk(dirsadded),
198 'dirsadded': pronk(dirsadded),
199 'filesremoved': pronk(filesremoved),
199 'filesremoved': pronk(filesremoved),
200 'linelengths': pronk(linelengths),
200 'linelengths': pronk(linelengths),
201 'parents': pronk(parents),
201 'parents': pronk(parents),
202 'p1distance': pronk(p1distance),
202 'p1distance': pronk(p1distance),
203 'p2distance': pronk(p2distance),
203 'p2distance': pronk(p2distance),
204 'interarrival': pronk(interarrival),
204 'interarrival': pronk(interarrival),
205 'tzoffset': pronk(tzoffset),
205 'tzoffset': pronk(tzoffset),
206 },
206 },
207 fp)
207 fp)
208 fp.close()
208 fp.close()
209
209
210 @command('synthesize',
210 @command('synthesize',
211 [('c', 'count', 0, _('create given number of commits'), _('COUNT')),
211 [('c', 'count', 0, _('create given number of commits'), _('COUNT')),
212 ('', 'dict', '', _('path to a dictionary of words'), _('FILE'))],
212 ('', 'dict', '', _('path to a dictionary of words'), _('FILE'))],
213 _('hg synthesize [OPTION].. DESCFILE'))
213 _('hg synthesize [OPTION].. DESCFILE'))
214 def synthesize(ui, repo, descpath, **opts):
214 def synthesize(ui, repo, descpath, **opts):
215 '''synthesize commits based on a model of an existing repository
215 '''synthesize commits based on a model of an existing repository
216
216
217 The model must have been generated by :hg:`analyze`. Commits will
217 The model must have been generated by :hg:`analyze`. Commits will
218 be generated randomly according to the probabilities described in
218 be generated randomly according to the probabilities described in
219 the model.
219 the model.
220
220
221 When synthesizing new content, commit descriptions, and user
221 When synthesizing new content, commit descriptions, and user
222 names, words will be chosen randomly from a dictionary that is
222 names, words will be chosen randomly from a dictionary that is
223 presumed to contain one word per line. Use --dict to specify the
223 presumed to contain one word per line. Use --dict to specify the
224 path to an alternate dictionary to use.
224 path to an alternate dictionary to use.
225 '''
225 '''
226 try:
226 try:
227 fp = hg.openpath(ui, descpath)
227 fp = hg.openpath(ui, descpath)
228 except Exception, err:
228 except Exception, err:
229 raise util.Abort('%s: %s' % (descpath, err[0].strerror))
229 raise util.Abort('%s: %s' % (descpath, err[0].strerror))
230 desc = json.load(fp)
230 desc = json.load(fp)
231 fp.close()
231 fp.close()
232
232
233 def cdf(l):
233 def cdf(l):
234 if not l:
234 if not l:
235 return [], []
235 return [], []
236 vals, probs = zip(*sorted(l, key=lambda x: x[1], reverse=True))
236 vals, probs = zip(*sorted(l, key=lambda x: x[1], reverse=True))
237 t = float(sum(probs, 0))
237 t = float(sum(probs, 0))
238 s, cdfs = 0, []
238 s, cdfs = 0, []
239 for v in probs:
239 for v in probs:
240 s += v
240 s += v
241 cdfs.append(s / t)
241 cdfs.append(s / t)
242 return vals, cdfs
242 return vals, cdfs
243
243
244 lineschanged = cdf(desc['lineschanged'])
244 lineschanged = cdf(desc['lineschanged'])
245 fileschanged = cdf(desc['fileschanged'])
245 fileschanged = cdf(desc['fileschanged'])
246 filesadded = cdf(desc['filesadded'])
246 filesadded = cdf(desc['filesadded'])
247 dirsadded = cdf(desc['dirsadded'])
247 dirsadded = cdf(desc['dirsadded'])
248 filesremoved = cdf(desc['filesremoved'])
248 filesremoved = cdf(desc['filesremoved'])
249 linelengths = cdf(desc['linelengths'])
249 linelengths = cdf(desc['linelengths'])
250 parents = cdf(desc['parents'])
250 parents = cdf(desc['parents'])
251 p1distance = cdf(desc['p1distance'])
251 p1distance = cdf(desc['p1distance'])
252 p2distance = cdf(desc['p2distance'])
252 p2distance = cdf(desc['p2distance'])
253 interarrival = cdf(desc['interarrival'])
253 interarrival = cdf(desc['interarrival'])
254 linesinfilesadded = cdf(desc['linesinfilesadded'])
254 linesinfilesadded = cdf(desc['linesinfilesadded'])
255 tzoffset = cdf(desc['tzoffset'])
255 tzoffset = cdf(desc['tzoffset'])
256
256
257 dictfile = opts.get('dict') or '/usr/share/dict/words'
257 dictfile = opts.get('dict') or '/usr/share/dict/words'
258 try:
258 try:
259 fp = open(dictfile, 'rU')
259 fp = open(dictfile, 'rU')
260 except IOError, err:
260 except IOError, err:
261 raise util.Abort('%s: %s' % (dictfile, err.strerror))
261 raise util.Abort('%s: %s' % (dictfile, err.strerror))
262 words = fp.read().splitlines()
262 words = fp.read().splitlines()
263 fp.close()
263 fp.close()
264
264
265 def pick(cdf):
265 def pick(cdf):
266 return cdf[0][bisect.bisect_left(cdf[1], random.random())]
266 return cdf[0][bisect.bisect_left(cdf[1], random.random())]
267
267
268 def makeline(minimum=0):
268 def makeline(minimum=0):
269 total = max(minimum, pick(linelengths))
269 total = max(minimum, pick(linelengths))
270 c, l = 0, []
270 c, l = 0, []
271 while c < total:
271 while c < total:
272 w = random.choice(words)
272 w = random.choice(words)
273 c += len(w) + 1
273 c += len(w) + 1
274 l.append(w)
274 l.append(w)
275 return ' '.join(l)
275 return ' '.join(l)
276
276
277 wlock = repo.wlock()
277 wlock = repo.wlock()
278 lock = repo.lock()
278 lock = repo.lock()
279
279
280 nevertouch = set(('.hgsub', '.hgignore', '.hgtags'))
280 nevertouch = set(('.hgsub', '.hgignore', '.hgtags'))
281
281
282 progress = ui.progress
282 progress = ui.progress
283 _synthesizing = _('synthesizing')
283 _synthesizing = _('synthesizing')
284 _changesets = _('changesets')
284 _changesets = _('changesets')
285
285
286 count = int(opts['count'])
286 count = int(opts['count'])
287 heads = set(map(repo.changelog.rev, repo.heads()))
287 heads = set(map(repo.changelog.rev, repo.heads()))
288 for i in xrange(count):
288 for i in xrange(count):
289 progress(_synthesizing, i, unit=_changesets, total=count)
289 progress(_synthesizing, i, unit=_changesets, total=count)
290
290
291 node = repo.changelog.node
291 node = repo.changelog.node
292 revs = len(repo)
292 revs = len(repo)
293
293
294 def pickhead(heads, distance):
294 def pickhead(heads, distance):
295 if heads:
295 if heads:
296 lheads = sorted(heads)
296 lheads = sorted(heads)
297 rev = revs - min(pick(distance), revs)
297 rev = revs - min(pick(distance), revs)
298 if rev < lheads[-1]:
298 if rev < lheads[-1]:
299 rev = lheads[bisect.bisect_left(lheads, rev)]
299 rev = lheads[bisect.bisect_left(lheads, rev)]
300 else:
300 else:
301 rev = lheads[-1]
301 rev = lheads[-1]
302 return rev, node(rev)
302 return rev, node(rev)
303 return nullrev, nullid
303 return nullrev, nullid
304
304
305 r1 = revs - min(pick(p1distance), revs)
305 r1 = revs - min(pick(p1distance), revs)
306 p1 = node(r1)
306 p1 = node(r1)
307
307
308 # the number of heads will grow without bound if we use a pure
308 # the number of heads will grow without bound if we use a pure
309 # model, so artificially constrain their proliferation
309 # model, so artificially constrain their proliferation
310 if pick(parents) == 2 or len(heads) > random.randint(1, 20):
310 toomanyheads = len(heads) > random.randint(1, 20)
311 if p2distance[0] and (pick(parents) == 2 or toomanyheads):
311 r2, p2 = pickhead(heads.difference([r1]), p2distance)
312 r2, p2 = pickhead(heads.difference([r1]), p2distance)
312 else:
313 else:
313 r2, p2 = nullrev, nullid
314 r2, p2 = nullrev, nullid
314
315
315 pl = [p1, p2]
316 pl = [p1, p2]
316 pctx = repo[r1]
317 pctx = repo[r1]
317 mf = pctx.manifest()
318 mf = pctx.manifest()
318 mfk = mf.keys()
319 mfk = mf.keys()
319 changes = {}
320 changes = {}
320 if mfk:
321 if mfk:
321 for __ in xrange(pick(fileschanged)):
322 for __ in xrange(pick(fileschanged)):
322 for __ in xrange(10):
323 for __ in xrange(10):
323 fctx = pctx.filectx(random.choice(mfk))
324 fctx = pctx.filectx(random.choice(mfk))
324 path = fctx.path()
325 path = fctx.path()
325 if not (path in nevertouch or fctx.isbinary() or
326 if not (path in nevertouch or fctx.isbinary() or
326 'l' in fctx.flags()):
327 'l' in fctx.flags()):
327 break
328 break
328 lines = fctx.data().splitlines()
329 lines = fctx.data().splitlines()
329 add, remove = pick(lineschanged)
330 add, remove = pick(lineschanged)
330 for __ in xrange(remove):
331 for __ in xrange(remove):
331 if not lines:
332 if not lines:
332 break
333 break
333 del lines[random.randrange(0, len(lines))]
334 del lines[random.randrange(0, len(lines))]
334 for __ in xrange(add):
335 for __ in xrange(add):
335 lines.insert(random.randint(0, len(lines)), makeline())
336 lines.insert(random.randint(0, len(lines)), makeline())
336 path = fctx.path()
337 path = fctx.path()
337 changes[path] = context.memfilectx(repo, path,
338 changes[path] = context.memfilectx(repo, path,
338 '\n'.join(lines) + '\n')
339 '\n'.join(lines) + '\n')
339 for __ in xrange(pick(filesremoved)):
340 for __ in xrange(pick(filesremoved)):
340 path = random.choice(mfk)
341 path = random.choice(mfk)
341 for __ in xrange(10):
342 for __ in xrange(10):
342 path = random.choice(mfk)
343 path = random.choice(mfk)
343 if path not in changes:
344 if path not in changes:
344 changes[path] = None
345 changes[path] = None
345 break
346 break
346 if filesadded:
347 if filesadded:
347 dirs = list(pctx.dirs())
348 dirs = list(pctx.dirs())
348 dirs.append('')
349 dirs.append('')
349 for __ in xrange(pick(filesadded)):
350 for __ in xrange(pick(filesadded)):
350 path = [random.choice(dirs)]
351 path = [random.choice(dirs)]
351 if pick(dirsadded):
352 if pick(dirsadded):
352 path.append(random.choice(words))
353 path.append(random.choice(words))
353 path.append(random.choice(words))
354 path.append(random.choice(words))
354 path = '/'.join(filter(None, path))
355 path = '/'.join(filter(None, path))
355 data = '\n'.join(makeline()
356 data = '\n'.join(makeline()
356 for __ in xrange(pick(linesinfilesadded))) + '\n'
357 for __ in xrange(pick(linesinfilesadded))) + '\n'
357 changes[path] = context.memfilectx(repo, path, data)
358 changes[path] = context.memfilectx(repo, path, data)
358 def filectxfn(repo, memctx, path):
359 def filectxfn(repo, memctx, path):
359 return changes[path]
360 return changes[path]
360 if not changes:
361 if not changes:
361 continue
362 continue
362 if revs:
363 if revs:
363 date = repo['tip'].date()[0] + pick(interarrival)
364 date = repo['tip'].date()[0] + pick(interarrival)
364 else:
365 else:
365 date = time.time() - (86400 * count)
366 date = time.time() - (86400 * count)
366 user = random.choice(words) + '@' + random.choice(words)
367 user = random.choice(words) + '@' + random.choice(words)
367 mc = context.memctx(repo, pl, makeline(minimum=2),
368 mc = context.memctx(repo, pl, makeline(minimum=2),
368 sorted(changes.iterkeys()),
369 sorted(changes.iterkeys()),
369 filectxfn, user, '%d %d' % (date, pick(tzoffset)))
370 filectxfn, user, '%d %d' % (date, pick(tzoffset)))
370 newnode = mc.commit()
371 newnode = mc.commit()
371 heads.add(repo.changelog.rev(newnode))
372 heads.add(repo.changelog.rev(newnode))
372 heads.discard(r1)
373 heads.discard(r1)
373 heads.discard(r2)
374 heads.discard(r2)
374
375
375 lock.release()
376 lock.release()
376 wlock.release()
377 wlock.release()
General Comments 0
You need to be logged in to leave comments. Login now