##// END OF EJS Templates
shelve: use rebase instead of merge (issue4068)...
Durham Goode -
r19961:1d7a36ff stable
parent child Browse files
Show More
@@ -1,640 +1,708 b''
1 # shelve.py - save/restore working directory state
1 # shelve.py - save/restore working directory state
2 #
2 #
3 # Copyright 2013 Facebook, Inc.
3 # Copyright 2013 Facebook, Inc.
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 """save and restore changes to the working directory
8 """save and restore changes to the working directory
9
9
10 The "hg shelve" command saves changes made to the working directory
10 The "hg shelve" command saves changes made to the working directory
11 and reverts those changes, resetting the working directory to a clean
11 and reverts those changes, resetting the working directory to a clean
12 state.
12 state.
13
13
14 Later on, the "hg unshelve" command restores the changes saved by "hg
14 Later on, the "hg unshelve" command restores the changes saved by "hg
15 shelve". Changes can be restored even after updating to a different
15 shelve". Changes can be restored even after updating to a different
16 parent, in which case Mercurial's merge machinery will resolve any
16 parent, in which case Mercurial's merge machinery will resolve any
17 conflicts if necessary.
17 conflicts if necessary.
18
18
19 You can have more than one shelved change outstanding at a time; each
19 You can have more than one shelved change outstanding at a time; each
20 shelved change has a distinct name. For details, see the help for "hg
20 shelved change has a distinct name. For details, see the help for "hg
21 shelve".
21 shelve".
22 """
22 """
23
23
24 from mercurial.i18n import _
24 from mercurial.i18n import _
25 from mercurial.node import nullid, bin, hex
25 from mercurial.node import nullid, bin, hex
26 from mercurial import changegroup, cmdutil, scmutil, phases
26 from mercurial import changegroup, cmdutil, scmutil, phases
27 from mercurial import error, hg, mdiff, merge, patch, repair, util
27 from mercurial import error, hg, mdiff, merge, patch, repair, util
28 from mercurial import templatefilters
28 from mercurial import templatefilters
29 from mercurial import lock as lockmod
29 from mercurial import lock as lockmod
30 from hgext import rebase
30 import errno
31 import errno
31
32
32 cmdtable = {}
33 cmdtable = {}
33 command = cmdutil.command(cmdtable)
34 command = cmdutil.command(cmdtable)
34 testedwith = 'internal'
35 testedwith = 'internal'
35
36
36 class shelvedfile(object):
37 class shelvedfile(object):
37 """Helper for the file storing a single shelve
38 """Helper for the file storing a single shelve
38
39
39 Handles common functions on shelve files (.hg/.files/.patch) using
40 Handles common functions on shelve files (.hg/.files/.patch) using
40 the vfs layer"""
41 the vfs layer"""
41 def __init__(self, repo, name, filetype=None):
42 def __init__(self, repo, name, filetype=None):
42 self.repo = repo
43 self.repo = repo
43 self.name = name
44 self.name = name
44 self.vfs = scmutil.vfs(repo.join('shelved'))
45 self.vfs = scmutil.vfs(repo.join('shelved'))
45 if filetype:
46 if filetype:
46 self.fname = name + '.' + filetype
47 self.fname = name + '.' + filetype
47 else:
48 else:
48 self.fname = name
49 self.fname = name
49
50
50 def exists(self):
51 def exists(self):
51 return self.vfs.exists(self.fname)
52 return self.vfs.exists(self.fname)
52
53
53 def filename(self):
54 def filename(self):
54 return self.vfs.join(self.fname)
55 return self.vfs.join(self.fname)
55
56
56 def unlink(self):
57 def unlink(self):
57 util.unlink(self.filename())
58 util.unlink(self.filename())
58
59
59 def stat(self):
60 def stat(self):
60 return self.vfs.stat(self.fname)
61 return self.vfs.stat(self.fname)
61
62
62 def opener(self, mode='rb'):
63 def opener(self, mode='rb'):
63 try:
64 try:
64 return self.vfs(self.fname, mode)
65 return self.vfs(self.fname, mode)
65 except IOError, err:
66 except IOError, err:
66 if err.errno != errno.ENOENT:
67 if err.errno != errno.ENOENT:
67 raise
68 raise
68 if mode[0] in 'wa':
69 if mode[0] in 'wa':
69 try:
70 try:
70 self.vfs.mkdir()
71 self.vfs.mkdir()
71 return self.vfs(self.fname, mode)
72 return self.vfs(self.fname, mode)
72 except IOError, err:
73 except IOError, err:
73 if err.errno != errno.EEXIST:
74 if err.errno != errno.EEXIST:
74 raise
75 raise
75 elif mode[0] == 'r':
76 elif mode[0] == 'r':
76 raise util.Abort(_("shelved change '%s' not found") %
77 raise util.Abort(_("shelved change '%s' not found") %
77 self.name)
78 self.name)
78
79
79 class shelvedstate(object):
80 class shelvedstate(object):
80 """Handle persistence during unshelving operations.
81 """Handle persistence during unshelving operations.
81
82
82 Handles saving and restoring a shelved state. Ensures that different
83 Handles saving and restoring a shelved state. Ensures that different
83 versions of a shelved state are possible and handles them appropriately.
84 versions of a shelved state are possible and handles them appropriately.
84 """
85 """
85 _version = 1
86 _version = 1
86 _filename = 'shelvedstate'
87 _filename = 'shelvedstate'
87
88
88 @classmethod
89 @classmethod
89 def load(cls, repo):
90 def load(cls, repo):
90 fp = repo.opener(cls._filename)
91 fp = repo.opener(cls._filename)
91 try:
92 try:
92 version = int(fp.readline().strip())
93 version = int(fp.readline().strip())
93
94
94 if version != cls._version:
95 if version != cls._version:
95 raise util.Abort(_('this version of shelve is incompatible '
96 raise util.Abort(_('this version of shelve is incompatible '
96 'with the version used in this repo'))
97 'with the version used in this repo'))
97 name = fp.readline().strip()
98 name = fp.readline().strip()
99 wctx = fp.readline().strip()
100 pendingctx = fp.readline().strip()
98 parents = [bin(h) for h in fp.readline().split()]
101 parents = [bin(h) for h in fp.readline().split()]
99 stripnodes = [bin(h) for h in fp.readline().split()]
102 stripnodes = [bin(h) for h in fp.readline().split()]
103 unknownfiles = fp.readline()[:-1].split('\0')
100 finally:
104 finally:
101 fp.close()
105 fp.close()
102
106
103 obj = cls()
107 obj = cls()
104 obj.name = name
108 obj.name = name
109 obj.wctx = repo[bin(wctx)]
110 obj.pendingctx = repo[bin(pendingctx)]
105 obj.parents = parents
111 obj.parents = parents
106 obj.stripnodes = stripnodes
112 obj.stripnodes = stripnodes
113 obj.unknownfiles = unknownfiles
107
114
108 return obj
115 return obj
109
116
110 @classmethod
117 @classmethod
111 def save(cls, repo, name, stripnodes):
118 def save(cls, repo, name, originalwctx, pendingctx, stripnodes,
119 unknownfiles):
112 fp = repo.opener(cls._filename, 'wb')
120 fp = repo.opener(cls._filename, 'wb')
113 fp.write('%i\n' % cls._version)
121 fp.write('%i\n' % cls._version)
114 fp.write('%s\n' % name)
122 fp.write('%s\n' % name)
123 fp.write('%s\n' % hex(originalwctx.node()))
124 fp.write('%s\n' % hex(pendingctx.node()))
115 fp.write('%s\n' % ' '.join([hex(p) for p in repo.dirstate.parents()]))
125 fp.write('%s\n' % ' '.join([hex(p) for p in repo.dirstate.parents()]))
116 fp.write('%s\n' % ' '.join([hex(n) for n in stripnodes]))
126 fp.write('%s\n' % ' '.join([hex(n) for n in stripnodes]))
127 fp.write('%s\n' % '\0'.join(unknownfiles))
117 fp.close()
128 fp.close()
118
129
119 @classmethod
130 @classmethod
120 def clear(cls, repo):
131 def clear(cls, repo):
121 util.unlinkpath(repo.join(cls._filename), ignoremissing=True)
132 util.unlinkpath(repo.join(cls._filename), ignoremissing=True)
122
133
123 def createcmd(ui, repo, pats, opts):
134 def createcmd(ui, repo, pats, opts):
124 """subcommand that creates a new shelve"""
135 """subcommand that creates a new shelve"""
125
136
126 def publicancestors(ctx):
137 def publicancestors(ctx):
127 """Compute the heads of the public ancestors of a commit.
138 """Compute the heads of the public ancestors of a commit.
128
139
129 Much faster than the revset heads(ancestors(ctx) - draft())"""
140 Much faster than the revset heads(ancestors(ctx) - draft())"""
130 seen = set()
141 seen = set()
131 visit = util.deque()
142 visit = util.deque()
132 visit.append(ctx)
143 visit.append(ctx)
133 while visit:
144 while visit:
134 ctx = visit.popleft()
145 ctx = visit.popleft()
135 for parent in ctx.parents():
146 for parent in ctx.parents():
136 rev = parent.rev()
147 rev = parent.rev()
137 if rev not in seen:
148 if rev not in seen:
138 seen.add(rev)
149 seen.add(rev)
139 if parent.mutable():
150 if parent.mutable():
140 visit.append(parent)
151 visit.append(parent)
141 else:
152 else:
142 yield parent.node()
153 yield parent.node()
143
154
144 wctx = repo[None]
155 wctx = repo[None]
145 parents = wctx.parents()
156 parents = wctx.parents()
146 if len(parents) > 1:
157 if len(parents) > 1:
147 raise util.Abort(_('cannot shelve while merging'))
158 raise util.Abort(_('cannot shelve while merging'))
148 parent = parents[0]
159 parent = parents[0]
149
160
150 # we never need the user, so we use a generic user for all shelve operations
161 # we never need the user, so we use a generic user for all shelve operations
151 user = 'shelve@localhost'
162 user = 'shelve@localhost'
152 label = repo._bookmarkcurrent or parent.branch() or 'default'
163 label = repo._bookmarkcurrent or parent.branch() or 'default'
153
164
154 # slashes aren't allowed in filenames, therefore we rename it
165 # slashes aren't allowed in filenames, therefore we rename it
155 origlabel, label = label, label.replace('/', '_')
166 origlabel, label = label, label.replace('/', '_')
156
167
157 def gennames():
168 def gennames():
158 yield label
169 yield label
159 for i in xrange(1, 100):
170 for i in xrange(1, 100):
160 yield '%s-%02d' % (label, i)
171 yield '%s-%02d' % (label, i)
161
172
162 shelvedfiles = []
173 shelvedfiles = []
163
174
164 def commitfunc(ui, repo, message, match, opts):
175 def commitfunc(ui, repo, message, match, opts):
165 # check modified, added, removed, deleted only
176 # check modified, added, removed, deleted only
166 for flist in repo.status(match=match)[:4]:
177 for flist in repo.status(match=match)[:4]:
167 shelvedfiles.extend(flist)
178 shelvedfiles.extend(flist)
168 hasmq = util.safehasattr(repo, 'mq')
179 hasmq = util.safehasattr(repo, 'mq')
169 if hasmq:
180 if hasmq:
170 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
181 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
171 try:
182 try:
172 return repo.commit(message, user, opts.get('date'), match)
183 return repo.commit(message, user, opts.get('date'), match)
173 finally:
184 finally:
174 if hasmq:
185 if hasmq:
175 repo.mq.checkapplied = saved
186 repo.mq.checkapplied = saved
176
187
177 if parent.node() != nullid:
188 if parent.node() != nullid:
178 desc = parent.description().split('\n', 1)[0]
189 desc = parent.description().split('\n', 1)[0]
179 else:
190 else:
180 desc = '(empty repository)'
191 desc = '(empty repository)'
181
192
182 if not opts['message']:
193 if not opts['message']:
183 opts['message'] = desc
194 opts['message'] = desc
184
195
185 name = opts['name']
196 name = opts['name']
186
197
187 wlock = lock = tr = bms = None
198 wlock = lock = tr = bms = None
188 try:
199 try:
189 wlock = repo.wlock()
200 wlock = repo.wlock()
190 lock = repo.lock()
201 lock = repo.lock()
191
202
192 bms = repo._bookmarks.copy()
203 bms = repo._bookmarks.copy()
193 # use an uncommitted transaction to generate the bundle to avoid
204 # use an uncommitted transaction to generate the bundle to avoid
194 # pull races. ensure we don't print the abort message to stderr.
205 # pull races. ensure we don't print the abort message to stderr.
195 tr = repo.transaction('commit', report=lambda x: None)
206 tr = repo.transaction('commit', report=lambda x: None)
196
207
197 if name:
208 if name:
198 if shelvedfile(repo, name, 'hg').exists():
209 if shelvedfile(repo, name, 'hg').exists():
199 raise util.Abort(_("a shelved change named '%s' already exists")
210 raise util.Abort(_("a shelved change named '%s' already exists")
200 % name)
211 % name)
201 else:
212 else:
202 for n in gennames():
213 for n in gennames():
203 if not shelvedfile(repo, n, 'hg').exists():
214 if not shelvedfile(repo, n, 'hg').exists():
204 name = n
215 name = n
205 break
216 break
206 else:
217 else:
207 raise util.Abort(_("too many shelved changes named '%s'") %
218 raise util.Abort(_("too many shelved changes named '%s'") %
208 label)
219 label)
209
220
210 # ensure we are not creating a subdirectory or a hidden file
221 # ensure we are not creating a subdirectory or a hidden file
211 if '/' in name or '\\' in name:
222 if '/' in name or '\\' in name:
212 raise util.Abort(_('shelved change names may not contain slashes'))
223 raise util.Abort(_('shelved change names may not contain slashes'))
213 if name.startswith('.'):
224 if name.startswith('.'):
214 raise util.Abort(_("shelved change names may not start with '.'"))
225 raise util.Abort(_("shelved change names may not start with '.'"))
215
226
216 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
227 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
217
228
218 if not node:
229 if not node:
219 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
230 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
220 if stat[3]:
231 if stat[3]:
221 ui.status(_("nothing changed (%d missing files, see "
232 ui.status(_("nothing changed (%d missing files, see "
222 "'hg status')\n") % len(stat[3]))
233 "'hg status')\n") % len(stat[3]))
223 else:
234 else:
224 ui.status(_("nothing changed\n"))
235 ui.status(_("nothing changed\n"))
225 return 1
236 return 1
226
237
227 phases.retractboundary(repo, phases.secret, [node])
238 phases.retractboundary(repo, phases.secret, [node])
228
239
229 fp = shelvedfile(repo, name, 'files').opener('wb')
240 fp = shelvedfile(repo, name, 'files').opener('wb')
230 fp.write('\0'.join(shelvedfiles))
241 fp.write('\0'.join(shelvedfiles))
231
242
232 bases = list(publicancestors(repo[node]))
243 bases = list(publicancestors(repo[node]))
233 cg = repo.changegroupsubset(bases, [node], 'shelve')
244 cg = repo.changegroupsubset(bases, [node], 'shelve')
234 changegroup.writebundle(cg, shelvedfile(repo, name, 'hg').filename(),
245 changegroup.writebundle(cg, shelvedfile(repo, name, 'hg').filename(),
235 'HG10UN')
246 'HG10UN')
236 cmdutil.export(repo, [node],
247 cmdutil.export(repo, [node],
237 fp=shelvedfile(repo, name, 'patch').opener('wb'),
248 fp=shelvedfile(repo, name, 'patch').opener('wb'),
238 opts=mdiff.diffopts(git=True))
249 opts=mdiff.diffopts(git=True))
239
250
240
251
241 if ui.formatted():
252 if ui.formatted():
242 desc = util.ellipsis(desc, ui.termwidth())
253 desc = util.ellipsis(desc, ui.termwidth())
243 ui.status(_('shelved as %s\n') % name)
254 ui.status(_('shelved as %s\n') % name)
244 hg.update(repo, parent.node())
255 hg.update(repo, parent.node())
245 finally:
256 finally:
246 if bms:
257 if bms:
247 # restore old bookmarks
258 # restore old bookmarks
248 repo._bookmarks.update(bms)
259 repo._bookmarks.update(bms)
249 repo._bookmarks.write()
260 repo._bookmarks.write()
250 if tr:
261 if tr:
251 tr.abort()
262 tr.abort()
252 lockmod.release(lock, wlock)
263 lockmod.release(lock, wlock)
253
264
254 def cleanupcmd(ui, repo):
265 def cleanupcmd(ui, repo):
255 """subcommand that deletes all shelves"""
266 """subcommand that deletes all shelves"""
256
267
257 wlock = None
268 wlock = None
258 try:
269 try:
259 wlock = repo.wlock()
270 wlock = repo.wlock()
260 for (name, _) in repo.vfs.readdir('shelved'):
271 for (name, _) in repo.vfs.readdir('shelved'):
261 suffix = name.rsplit('.', 1)[-1]
272 suffix = name.rsplit('.', 1)[-1]
262 if suffix in ('hg', 'files', 'patch'):
273 if suffix in ('hg', 'files', 'patch'):
263 shelvedfile(repo, name).unlink()
274 shelvedfile(repo, name).unlink()
264 finally:
275 finally:
265 lockmod.release(wlock)
276 lockmod.release(wlock)
266
277
267 def deletecmd(ui, repo, pats):
278 def deletecmd(ui, repo, pats):
268 """subcommand that deletes a specific shelve"""
279 """subcommand that deletes a specific shelve"""
269 if not pats:
280 if not pats:
270 raise util.Abort(_('no shelved changes specified!'))
281 raise util.Abort(_('no shelved changes specified!'))
271 wlock = None
282 wlock = None
272 try:
283 try:
273 wlock = repo.wlock()
284 wlock = repo.wlock()
274 try:
285 try:
275 for name in pats:
286 for name in pats:
276 for suffix in 'hg files patch'.split():
287 for suffix in 'hg files patch'.split():
277 shelvedfile(repo, name, suffix).unlink()
288 shelvedfile(repo, name, suffix).unlink()
278 except OSError, err:
289 except OSError, err:
279 if err.errno != errno.ENOENT:
290 if err.errno != errno.ENOENT:
280 raise
291 raise
281 raise util.Abort(_("shelved change '%s' not found") % name)
292 raise util.Abort(_("shelved change '%s' not found") % name)
282 finally:
293 finally:
283 lockmod.release(wlock)
294 lockmod.release(wlock)
284
295
285 def listshelves(repo):
296 def listshelves(repo):
286 """return all shelves in repo as list of (time, filename)"""
297 """return all shelves in repo as list of (time, filename)"""
287 try:
298 try:
288 names = repo.vfs.readdir('shelved')
299 names = repo.vfs.readdir('shelved')
289 except OSError, err:
300 except OSError, err:
290 if err.errno != errno.ENOENT:
301 if err.errno != errno.ENOENT:
291 raise
302 raise
292 return []
303 return []
293 info = []
304 info = []
294 for (name, _) in names:
305 for (name, _) in names:
295 pfx, sfx = name.rsplit('.', 1)
306 pfx, sfx = name.rsplit('.', 1)
296 if not pfx or sfx != 'patch':
307 if not pfx or sfx != 'patch':
297 continue
308 continue
298 st = shelvedfile(repo, name).stat()
309 st = shelvedfile(repo, name).stat()
299 info.append((st.st_mtime, shelvedfile(repo, pfx).filename()))
310 info.append((st.st_mtime, shelvedfile(repo, pfx).filename()))
300 return sorted(info, reverse=True)
311 return sorted(info, reverse=True)
301
312
302 def listcmd(ui, repo, pats, opts):
313 def listcmd(ui, repo, pats, opts):
303 """subcommand that displays the list of shelves"""
314 """subcommand that displays the list of shelves"""
304 pats = set(pats)
315 pats = set(pats)
305 width = 80
316 width = 80
306 if not ui.plain():
317 if not ui.plain():
307 width = ui.termwidth()
318 width = ui.termwidth()
308 namelabel = 'shelve.newest'
319 namelabel = 'shelve.newest'
309 for mtime, name in listshelves(repo):
320 for mtime, name in listshelves(repo):
310 sname = util.split(name)[1]
321 sname = util.split(name)[1]
311 if pats and sname not in pats:
322 if pats and sname not in pats:
312 continue
323 continue
313 ui.write(sname, label=namelabel)
324 ui.write(sname, label=namelabel)
314 namelabel = 'shelve.name'
325 namelabel = 'shelve.name'
315 if ui.quiet:
326 if ui.quiet:
316 ui.write('\n')
327 ui.write('\n')
317 continue
328 continue
318 ui.write(' ' * (16 - len(sname)))
329 ui.write(' ' * (16 - len(sname)))
319 used = 16
330 used = 16
320 age = '(%s)' % templatefilters.age(util.makedate(mtime), abbrev=True)
331 age = '(%s)' % templatefilters.age(util.makedate(mtime), abbrev=True)
321 ui.write(age, label='shelve.age')
332 ui.write(age, label='shelve.age')
322 ui.write(' ' * (12 - len(age)))
333 ui.write(' ' * (12 - len(age)))
323 used += 12
334 used += 12
324 fp = open(name + '.patch', 'rb')
335 fp = open(name + '.patch', 'rb')
325 try:
336 try:
326 while True:
337 while True:
327 line = fp.readline()
338 line = fp.readline()
328 if not line:
339 if not line:
329 break
340 break
330 if not line.startswith('#'):
341 if not line.startswith('#'):
331 desc = line.rstrip()
342 desc = line.rstrip()
332 if ui.formatted():
343 if ui.formatted():
333 desc = util.ellipsis(desc, width - used)
344 desc = util.ellipsis(desc, width - used)
334 ui.write(desc)
345 ui.write(desc)
335 break
346 break
336 ui.write('\n')
347 ui.write('\n')
337 if not (opts['patch'] or opts['stat']):
348 if not (opts['patch'] or opts['stat']):
338 continue
349 continue
339 difflines = fp.readlines()
350 difflines = fp.readlines()
340 if opts['patch']:
351 if opts['patch']:
341 for chunk, label in patch.difflabel(iter, difflines):
352 for chunk, label in patch.difflabel(iter, difflines):
342 ui.write(chunk, label=label)
353 ui.write(chunk, label=label)
343 if opts['stat']:
354 if opts['stat']:
344 for chunk, label in patch.diffstatui(difflines, width=width,
355 for chunk, label in patch.diffstatui(difflines, width=width,
345 git=True):
356 git=True):
346 ui.write(chunk, label=label)
357 ui.write(chunk, label=label)
347 finally:
358 finally:
348 fp.close()
359 fp.close()
349
360
350 def readshelvedfiles(repo, basename):
361 def readshelvedfiles(repo, basename):
351 """return the list of files touched in a shelve"""
362 """return the list of files touched in a shelve"""
352 fp = shelvedfile(repo, basename, 'files').opener()
363 fp = shelvedfile(repo, basename, 'files').opener()
353 return fp.read().split('\0')
364 return fp.read().split('\0')
354
365
355 def checkparents(repo, state):
366 def checkparents(repo, state):
356 """check parent while resuming an unshelve"""
367 """check parent while resuming an unshelve"""
357 if state.parents != repo.dirstate.parents():
368 if state.parents != repo.dirstate.parents():
358 raise util.Abort(_('working directory parents do not match unshelve '
369 raise util.Abort(_('working directory parents do not match unshelve '
359 'state'))
370 'state'))
360
371
361 def pathtofiles(repo, files):
372 def pathtofiles(repo, files):
362 cwd = repo.getcwd()
373 cwd = repo.getcwd()
363 return [repo.pathto(f, cwd) for f in files]
374 return [repo.pathto(f, cwd) for f in files]
364
375
365 def unshelveabort(ui, repo, state, opts):
376 def unshelveabort(ui, repo, state, opts):
366 """subcommand that abort an in-progress unshelve"""
377 """subcommand that abort an in-progress unshelve"""
367 wlock = repo.wlock()
378 wlock = repo.wlock()
368 lock = None
379 lock = None
369 try:
380 try:
370 checkparents(repo, state)
381 checkparents(repo, state)
382
383 util.rename(repo.join('unshelverebasestate'),
384 repo.join('rebasestate'))
385 try:
386 rebase.rebase(ui, repo, **{
387 'abort' : True
388 })
389 except Exception:
390 util.rename(repo.join('rebasestate'),
391 repo.join('unshelverebasestate'))
392 raise
393
371 lock = repo.lock()
394 lock = repo.lock()
372 merge.mergestate(repo).reset()
395
373 if opts['keep']:
396 mergefiles(ui, repo, state.wctx, state.pendingctx, state.unknownfiles)
374 repo.setparents(repo.dirstate.parents()[0])
397
375 else:
376 revertfiles = readshelvedfiles(repo, state.name)
377 wctx = repo.parents()[0]
378 cmdutil.revert(ui, repo, wctx, [wctx.node(), nullid],
379 *pathtofiles(repo, revertfiles),
380 **{'no_backup': True})
381 # fix up the weird dirstate states the merge left behind
382 mf = wctx.manifest()
383 dirstate = repo.dirstate
384 for f in revertfiles:
385 if f in mf:
386 dirstate.normallookup(f)
387 else:
388 dirstate.drop(f)
389 dirstate._pl = (wctx.node(), nullid)
390 dirstate._dirty = True
391 repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve')
398 repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve')
392 shelvedstate.clear(repo)
399 shelvedstate.clear(repo)
393 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
400 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
394 finally:
401 finally:
395 lockmod.release(lock, wlock)
402 lockmod.release(lock, wlock)
396
403
404 def mergefiles(ui, repo, wctx, shelvectx, unknownfiles):
405 """updates to wctx and merges the changes from shelvectx into the
406 dirstate. drops any files in unknownfiles from the dirstate."""
407 oldquiet = ui.quiet
408 try:
409 ui.quiet = True
410 hg.update(repo, wctx.node())
411 files = []
412 files.extend(shelvectx.files())
413 files.extend(shelvectx.parents()[0].files())
414 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(),
415 *pathtofiles(repo, files),
416 **{'no_backup': True})
417 finally:
418 ui.quiet = oldquiet
419
420 # Send untracked files back to being untracked
421 dirstate = repo.dirstate
422 for f in unknownfiles:
423 dirstate.drop(f)
424
397 def unshelvecleanup(ui, repo, name, opts):
425 def unshelvecleanup(ui, repo, name, opts):
398 """remove related files after an unshelve"""
426 """remove related files after an unshelve"""
399 if not opts['keep']:
427 if not opts['keep']:
400 for filetype in 'hg files patch'.split():
428 for filetype in 'hg files patch'.split():
401 shelvedfile(repo, name, filetype).unlink()
429 shelvedfile(repo, name, filetype).unlink()
402
430
403 def finishmerge(ui, repo, ms, stripnodes, name, opts):
404 # Reset the working dir so it's no longer in a merge state.
405 dirstate = repo.dirstate
406 dirstate.setparents(dirstate._pl[0])
407 shelvedstate.clear(repo)
408
409 def unshelvecontinue(ui, repo, state, opts):
431 def unshelvecontinue(ui, repo, state, opts):
410 """subcommand to continue an in-progress unshelve"""
432 """subcommand to continue an in-progress unshelve"""
411 # We're finishing off a merge. First parent is our original
433 # We're finishing off a merge. First parent is our original
412 # parent, second is the temporary "fake" commit we're unshelving.
434 # parent, second is the temporary "fake" commit we're unshelving.
413 wlock = repo.wlock()
435 wlock = repo.wlock()
414 lock = None
436 lock = None
415 try:
437 try:
416 checkparents(repo, state)
438 checkparents(repo, state)
417 ms = merge.mergestate(repo)
439 ms = merge.mergestate(repo)
418 if [f for f in ms if ms[f] == 'u']:
440 if [f for f in ms if ms[f] == 'u']:
419 raise util.Abort(
441 raise util.Abort(
420 _("unresolved conflicts, can't continue"),
442 _("unresolved conflicts, can't continue"),
421 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
443 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
422 finishmerge(ui, repo, ms, state.stripnodes, state.name, opts)
444
423 lock = repo.lock()
445 lock = repo.lock()
446
447 util.rename(repo.join('unshelverebasestate'),
448 repo.join('rebasestate'))
449 try:
450 rebase.rebase(ui, repo, **{
451 'continue' : True
452 })
453 except Exception:
454 util.rename(repo.join('rebasestate'),
455 repo.join('unshelverebasestate'))
456 raise
457
458 shelvectx = repo['tip']
459 if not shelvectx in state.pendingctx.children():
460 # rebase was a no-op, so it produced no child commit
461 shelvectx = state.pendingctx
462
463 mergefiles(ui, repo, state.wctx, shelvectx, state.unknownfiles)
464
465 state.stripnodes.append(shelvectx.node())
424 repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve')
466 repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve')
467 shelvedstate.clear(repo)
425 unshelvecleanup(ui, repo, state.name, opts)
468 unshelvecleanup(ui, repo, state.name, opts)
426 ui.status(_("unshelve of '%s' complete\n") % state.name)
469 ui.status(_("unshelve of '%s' complete\n") % state.name)
427 finally:
470 finally:
428 lockmod.release(lock, wlock)
471 lockmod.release(lock, wlock)
429
472
430 @command('unshelve',
473 @command('unshelve',
431 [('a', 'abort', None,
474 [('a', 'abort', None,
432 _('abort an incomplete unshelve operation')),
475 _('abort an incomplete unshelve operation')),
433 ('c', 'continue', None,
476 ('c', 'continue', None,
434 _('continue an incomplete unshelve operation')),
477 _('continue an incomplete unshelve operation')),
435 ('', 'keep', None,
478 ('', 'keep', None,
436 _('keep shelve after unshelving'))],
479 _('keep shelve after unshelving'))],
437 _('hg unshelve [SHELVED]'))
480 _('hg unshelve [SHELVED]'))
438 def unshelve(ui, repo, *shelved, **opts):
481 def unshelve(ui, repo, *shelved, **opts):
439 """restore a shelved change to the working directory
482 """restore a shelved change to the working directory
440
483
441 This command accepts an optional name of a shelved change to
484 This command accepts an optional name of a shelved change to
442 restore. If none is given, the most recent shelved change is used.
485 restore. If none is given, the most recent shelved change is used.
443
486
444 If a shelved change is applied successfully, the bundle that
487 If a shelved change is applied successfully, the bundle that
445 contains the shelved changes is deleted afterwards.
488 contains the shelved changes is deleted afterwards.
446
489
447 Since you can restore a shelved change on top of an arbitrary
490 Since you can restore a shelved change on top of an arbitrary
448 commit, it is possible that unshelving will result in a conflict
491 commit, it is possible that unshelving will result in a conflict
449 between your changes and the commits you are unshelving onto. If
492 between your changes and the commits you are unshelving onto. If
450 this occurs, you must resolve the conflict, then use
493 this occurs, you must resolve the conflict, then use
451 ``--continue`` to complete the unshelve operation. (The bundle
494 ``--continue`` to complete the unshelve operation. (The bundle
452 will not be deleted until you successfully complete the unshelve.)
495 will not be deleted until you successfully complete the unshelve.)
453
496
454 (Alternatively, you can use ``--abort`` to abandon an unshelve
497 (Alternatively, you can use ``--abort`` to abandon an unshelve
455 that causes a conflict. This reverts the unshelved changes, and
498 that causes a conflict. This reverts the unshelved changes, and
456 does not delete the bundle.)
499 does not delete the bundle.)
457 """
500 """
458 abortf = opts['abort']
501 abortf = opts['abort']
459 continuef = opts['continue']
502 continuef = opts['continue']
460 if not abortf and not continuef:
503 if not abortf and not continuef:
461 cmdutil.checkunfinished(repo)
504 cmdutil.checkunfinished(repo)
462
505
463 if abortf or continuef:
506 if abortf or continuef:
464 if abortf and continuef:
507 if abortf and continuef:
465 raise util.Abort(_('cannot use both abort and continue'))
508 raise util.Abort(_('cannot use both abort and continue'))
466 if shelved:
509 if shelved:
467 raise util.Abort(_('cannot combine abort/continue with '
510 raise util.Abort(_('cannot combine abort/continue with '
468 'naming a shelved change'))
511 'naming a shelved change'))
469
512
470 try:
513 try:
471 state = shelvedstate.load(repo)
514 state = shelvedstate.load(repo)
472 except IOError, err:
515 except IOError, err:
473 if err.errno != errno.ENOENT:
516 if err.errno != errno.ENOENT:
474 raise
517 raise
475 raise util.Abort(_('no unshelve operation underway'))
518 raise util.Abort(_('no unshelve operation underway'))
476
519
477 if abortf:
520 if abortf:
478 return unshelveabort(ui, repo, state, opts)
521 return unshelveabort(ui, repo, state, opts)
479 elif continuef:
522 elif continuef:
480 return unshelvecontinue(ui, repo, state, opts)
523 return unshelvecontinue(ui, repo, state, opts)
481 elif len(shelved) > 1:
524 elif len(shelved) > 1:
482 raise util.Abort(_('can only unshelve one change at a time'))
525 raise util.Abort(_('can only unshelve one change at a time'))
483 elif not shelved:
526 elif not shelved:
484 shelved = listshelves(repo)
527 shelved = listshelves(repo)
485 if not shelved:
528 if not shelved:
486 raise util.Abort(_('no shelved changes to apply!'))
529 raise util.Abort(_('no shelved changes to apply!'))
487 basename = util.split(shelved[0][1])[1]
530 basename = util.split(shelved[0][1])[1]
488 ui.status(_("unshelving change '%s'\n") % basename)
531 ui.status(_("unshelving change '%s'\n") % basename)
489 else:
532 else:
490 basename = shelved[0]
533 basename = shelved[0]
491
534
492 shelvedfiles = readshelvedfiles(repo, basename)
535 shelvedfiles = readshelvedfiles(repo, basename)
493
536
494 m, a, r, d = repo.status()[:4]
495 unsafe = set(m + a + r + d).intersection(shelvedfiles)
496 if unsafe:
497 ui.warn(_('the following shelved files have been modified:\n'))
498 for f in sorted(unsafe):
499 ui.warn(' %s\n' % f)
500 ui.warn(_('you must commit, revert, or shelve your changes before you '
501 'can proceed\n'))
502 raise util.Abort(_('cannot unshelve due to local changes\n'))
503
504 wlock = lock = tr = None
537 wlock = lock = tr = None
505 try:
538 try:
506 lock = repo.lock()
539 lock = repo.lock()
540 wlock = repo.wlock()
507
541
508 tr = repo.transaction('unshelve', report=lambda x: None)
542 tr = repo.transaction('unshelve', report=lambda x: None)
509 oldtiprev = len(repo)
543 oldtiprev = len(repo)
544
545 wctx = repo['.']
546 tmpwctx = wctx
547 # The goal is to have a commit structure like so:
548 # ...-> wctx -> tmpwctx -> shelvectx
549 # where tmpwctx is an optional commit with the user's pending changes
550 # and shelvectx is the unshelved changes. Then we merge it all down
551 # to the original wctx.
552
553 # Store pending changes in a commit
554 m, a, r, d, u = repo.status(unknown=True)[:5]
555 if m or a or r or d or u:
556 def commitfunc(ui, repo, message, match, opts):
557 hasmq = util.safehasattr(repo, 'mq')
558 if hasmq:
559 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
560
561 try:
562 return repo.commit(message, 'shelve@localhost',
563 opts.get('date'), match)
564 finally:
565 if hasmq:
566 repo.mq.checkapplied = saved
567
568 tempopts = {}
569 tempopts['message'] = "pending changes temporary commit"
570 tempopts['addremove'] = True
571 oldquiet = ui.quiet
572 try:
573 ui.quiet = True
574 node = cmdutil.commit(ui, repo, commitfunc, None, tempopts)
575 finally:
576 ui.quiet = oldquiet
577 tmpwctx = repo[node]
578
510 try:
579 try:
511 fp = shelvedfile(repo, basename, 'hg').opener()
580 fp = shelvedfile(repo, basename, 'hg').opener()
512 gen = changegroup.readbundle(fp, fp.name)
581 gen = changegroup.readbundle(fp, fp.name)
513 repo.addchangegroup(gen, 'unshelve', 'bundle:' + fp.name)
582 repo.addchangegroup(gen, 'unshelve', 'bundle:' + fp.name)
514 nodes = [ctx.node() for ctx in repo.set('%d:', oldtiprev)]
583 nodes = [ctx.node() for ctx in repo.set('%d:', oldtiprev)]
515 phases.retractboundary(repo, phases.secret, nodes)
584 phases.retractboundary(repo, phases.secret, nodes)
516 tr.close()
517 finally:
585 finally:
518 fp.close()
586 fp.close()
519
587
520 tip = repo['tip']
588 shelvectx = repo['tip']
521 wctx = repo['.']
522 ancestor = tip.ancestor(wctx)
523
524 wlock = repo.wlock()
525
589
526 if ancestor.node() != wctx.node():
590 # If the shelve is not immediately on top of the commit
527 conflicts = hg.merge(repo, tip.node(), force=True, remind=False)
591 # we'll be merging with, rebase it to be on top.
528 ms = merge.mergestate(repo)
592 if tmpwctx.node() != shelvectx.parents()[0].node():
529 stripnodes = [repo.changelog.node(rev)
593 try:
530 for rev in xrange(oldtiprev, len(repo))]
594 rebase.rebase(ui, repo, **{
531 if conflicts:
595 'rev' : [shelvectx.rev()],
532 shelvedstate.save(repo, basename, stripnodes)
596 'dest' : str(tmpwctx.rev()),
533 # Fix up the dirstate entries of files from the second
597 'keep' : True,
534 # parent as if we were not merging, except for those
598 })
535 # with unresolved conflicts.
599 except error.InterventionRequired:
536 parents = repo.parents()
600 tr.close()
537 revertfiles = set(parents[1].files()).difference(ms)
601
538 cmdutil.revert(ui, repo, parents[1],
602 stripnodes = [repo.changelog.node(rev)
539 (parents[0].node(), nullid),
603 for rev in xrange(oldtiprev, len(repo))]
540 *pathtofiles(repo, revertfiles),
604 shelvedstate.save(repo, basename, wctx, tmpwctx, stripnodes, u)
541 **{'no_backup': True})
605
606 util.rename(repo.join('rebasestate'),
607 repo.join('unshelverebasestate'))
542 raise error.InterventionRequired(
608 raise error.InterventionRequired(
543 _("unresolved conflicts (see 'hg resolve', then "
609 _("unresolved conflicts (see 'hg resolve', then "
544 "'hg unshelve --continue')"))
610 "'hg unshelve --continue')"))
545 finishmerge(ui, repo, ms, stripnodes, basename, opts)
611
546 else:
612 # refresh ctx after rebase completes
547 parent = tip.parents()[0]
613 shelvectx = repo['tip']
548 hg.update(repo, parent.node())
614
549 cmdutil.revert(ui, repo, tip, repo.dirstate.parents(),
615 if not shelvectx in tmpwctx.children():
550 *pathtofiles(repo, tip.files()),
616 # rebase was a no-op, so it produced no child commit
551 **{'no_backup': True})
617 shelvectx = tmpwctx
552
618
553 prevquiet = ui.quiet
619 mergefiles(ui, repo, wctx, shelvectx, u)
554 ui.quiet = True
620 shelvedstate.clear(repo)
555 try:
621
556 repo.rollback(force=True)
622 # The transaction aborting will strip all the commits for us,
557 finally:
623 # but it doesn't update the inmemory structures, so addchangegroup
558 ui.quiet = prevquiet
624 # hooks still fire and try to operate on the missing commits.
625 # Clean up manually to prevent this.
626 repo.changelog.strip(oldtiprev, tr)
559
627
560 unshelvecleanup(ui, repo, basename, opts)
628 unshelvecleanup(ui, repo, basename, opts)
561 finally:
629 finally:
562 if tr:
630 if tr:
563 tr.release()
631 tr.release()
564 lockmod.release(lock, wlock)
632 lockmod.release(lock, wlock)
565
633
566 @command('shelve',
634 @command('shelve',
567 [('A', 'addremove', None,
635 [('A', 'addremove', None,
568 _('mark new/missing files as added/removed before shelving')),
636 _('mark new/missing files as added/removed before shelving')),
569 ('', 'cleanup', None,
637 ('', 'cleanup', None,
570 _('delete all shelved changes')),
638 _('delete all shelved changes')),
571 ('', 'date', '',
639 ('', 'date', '',
572 _('shelve with the specified commit date'), _('DATE')),
640 _('shelve with the specified commit date'), _('DATE')),
573 ('d', 'delete', None,
641 ('d', 'delete', None,
574 _('delete the named shelved change(s)')),
642 _('delete the named shelved change(s)')),
575 ('l', 'list', None,
643 ('l', 'list', None,
576 _('list current shelves')),
644 _('list current shelves')),
577 ('m', 'message', '',
645 ('m', 'message', '',
578 _('use text as shelve message'), _('TEXT')),
646 _('use text as shelve message'), _('TEXT')),
579 ('n', 'name', '',
647 ('n', 'name', '',
580 _('use the given name for the shelved commit'), _('NAME')),
648 _('use the given name for the shelved commit'), _('NAME')),
581 ('p', 'patch', None,
649 ('p', 'patch', None,
582 _('show patch')),
650 _('show patch')),
583 ('', 'stat', None,
651 ('', 'stat', None,
584 _('output diffstat-style summary of changes'))],
652 _('output diffstat-style summary of changes'))],
585 _('hg shelve'))
653 _('hg shelve'))
586 def shelvecmd(ui, repo, *pats, **opts):
654 def shelvecmd(ui, repo, *pats, **opts):
587 '''save and set aside changes from the working directory
655 '''save and set aside changes from the working directory
588
656
589 Shelving takes files that "hg status" reports as not clean, saves
657 Shelving takes files that "hg status" reports as not clean, saves
590 the modifications to a bundle (a shelved change), and reverts the
658 the modifications to a bundle (a shelved change), and reverts the
591 files so that their state in the working directory becomes clean.
659 files so that their state in the working directory becomes clean.
592
660
593 To restore these changes to the working directory, using "hg
661 To restore these changes to the working directory, using "hg
594 unshelve"; this will work even if you switch to a different
662 unshelve"; this will work even if you switch to a different
595 commit.
663 commit.
596
664
597 When no files are specified, "hg shelve" saves all not-clean
665 When no files are specified, "hg shelve" saves all not-clean
598 files. If specific files or directories are named, only changes to
666 files. If specific files or directories are named, only changes to
599 those files are shelved.
667 those files are shelved.
600
668
601 Each shelved change has a name that makes it easier to find later.
669 Each shelved change has a name that makes it easier to find later.
602 The name of a shelved change defaults to being based on the active
670 The name of a shelved change defaults to being based on the active
603 bookmark, or if there is no active bookmark, the current named
671 bookmark, or if there is no active bookmark, the current named
604 branch. To specify a different name, use ``--name``.
672 branch. To specify a different name, use ``--name``.
605
673
606 To see a list of existing shelved changes, use the ``--list``
674 To see a list of existing shelved changes, use the ``--list``
607 option. For each shelved change, this will print its name, age,
675 option. For each shelved change, this will print its name, age,
608 and description; use ``--patch`` or ``--stat`` for more details.
676 and description; use ``--patch`` or ``--stat`` for more details.
609
677
610 To delete specific shelved changes, use ``--delete``. To delete
678 To delete specific shelved changes, use ``--delete``. To delete
611 all shelved changes, use ``--cleanup``.
679 all shelved changes, use ``--cleanup``.
612 '''
680 '''
613 cmdutil.checkunfinished(repo)
681 cmdutil.checkunfinished(repo)
614
682
615 def checkopt(opt, incompatible):
683 def checkopt(opt, incompatible):
616 if opts[opt]:
684 if opts[opt]:
617 for i in incompatible.split():
685 for i in incompatible.split():
618 if opts[i]:
686 if opts[i]:
619 raise util.Abort(_("options '--%s' and '--%s' may not be "
687 raise util.Abort(_("options '--%s' and '--%s' may not be "
620 "used together") % (opt, i))
688 "used together") % (opt, i))
621 return True
689 return True
622 if checkopt('cleanup', 'addremove delete list message name patch stat'):
690 if checkopt('cleanup', 'addremove delete list message name patch stat'):
623 if pats:
691 if pats:
624 raise util.Abort(_("cannot specify names when using '--cleanup'"))
692 raise util.Abort(_("cannot specify names when using '--cleanup'"))
625 return cleanupcmd(ui, repo)
693 return cleanupcmd(ui, repo)
626 elif checkopt('delete', 'addremove cleanup list message name patch stat'):
694 elif checkopt('delete', 'addremove cleanup list message name patch stat'):
627 return deletecmd(ui, repo, pats)
695 return deletecmd(ui, repo, pats)
628 elif checkopt('list', 'addremove cleanup delete message name'):
696 elif checkopt('list', 'addremove cleanup delete message name'):
629 return listcmd(ui, repo, pats, opts)
697 return listcmd(ui, repo, pats, opts)
630 else:
698 else:
631 for i in ('patch', 'stat'):
699 for i in ('patch', 'stat'):
632 if opts[i]:
700 if opts[i]:
633 raise util.Abort(_("option '--%s' may not be "
701 raise util.Abort(_("option '--%s' may not be "
634 "used when shelving a change") % (i,))
702 "used when shelving a change") % (i,))
635 return createcmd(ui, repo, pats, opts)
703 return createcmd(ui, repo, pats, opts)
636
704
637 def extsetup(ui):
705 def extsetup(ui):
638 cmdutil.unfinishedstates.append(
706 cmdutil.unfinishedstates.append(
639 [shelvedstate._filename, False, True, _('unshelve already in progress'),
707 [shelvedstate._filename, False, True, _('unshelve already in progress'),
640 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
708 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
@@ -1,486 +1,516 b''
1 $ echo "[extensions]" >> $HGRCPATH
1 $ echo "[extensions]" >> $HGRCPATH
2 $ echo "mq=" >> $HGRCPATH
2 $ echo "mq=" >> $HGRCPATH
3 $ echo "shelve=" >> $HGRCPATH
3 $ echo "shelve=" >> $HGRCPATH
4 $ echo "[defaults]" >> $HGRCPATH
4 $ echo "[defaults]" >> $HGRCPATH
5 $ echo "diff = --nodates --git" >> $HGRCPATH
5 $ echo "diff = --nodates --git" >> $HGRCPATH
6 $ echo "qnew = --date '0 0'" >> $HGRCPATH
6 $ echo "qnew = --date '0 0'" >> $HGRCPATH
7
7
8 $ hg init repo
8 $ hg init repo
9 $ cd repo
9 $ cd repo
10 $ mkdir a b
10 $ mkdir a b
11 $ echo a > a/a
11 $ echo a > a/a
12 $ echo b > b/b
12 $ echo b > b/b
13 $ echo c > c
13 $ echo c > c
14 $ echo d > d
14 $ echo d > d
15 $ echo x > x
15 $ echo x > x
16 $ hg addremove -q
16 $ hg addremove -q
17
17
18 shelving in an empty repo should be possible
18 shelving in an empty repo should be possible
19
19
20 $ hg shelve
20 $ hg shelve
21 shelved as default
21 shelved as default
22 0 files updated, 0 files merged, 5 files removed, 0 files unresolved
22 0 files updated, 0 files merged, 5 files removed, 0 files unresolved
23
23
24 $ hg unshelve
24 $ hg unshelve
25 unshelving change 'default'
25 unshelving change 'default'
26 adding changesets
26 adding changesets
27 adding manifests
27 adding manifests
28 adding file changes
28 adding file changes
29 added 1 changesets with 5 changes to 5 files
29 added 1 changesets with 5 changes to 5 files
30 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
31
30
32 $ hg commit -q -m 'initial commit'
31 $ hg commit -q -m 'initial commit'
33
32
34 $ hg shelve
33 $ hg shelve
35 nothing changed
34 nothing changed
36 [1]
35 [1]
37
36
38 create an mq patch - shelving should work fine with a patch applied
37 create an mq patch - shelving should work fine with a patch applied
39
38
40 $ echo n > n
39 $ echo n > n
41 $ hg add n
40 $ hg add n
42 $ hg commit n -m second
41 $ hg commit n -m second
43 $ hg qnew second.patch
42 $ hg qnew second.patch
44
43
45 shelve a change that we will delete later
44 shelve a change that we will delete later
46
45
47 $ echo a >> a/a
46 $ echo a >> a/a
48 $ hg shelve
47 $ hg shelve
49 shelved as default
48 shelved as default
50 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
49 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
51
50
52 set up some more complex changes to shelve
51 set up some more complex changes to shelve
53
52
54 $ echo a >> a/a
53 $ echo a >> a/a
55 $ hg mv b b.rename
54 $ hg mv b b.rename
56 moving b/b to b.rename/b (glob)
55 moving b/b to b.rename/b (glob)
57 $ hg cp c c.copy
56 $ hg cp c c.copy
58 $ hg status -C
57 $ hg status -C
59 M a/a
58 M a/a
60 A b.rename/b
59 A b.rename/b
61 b/b
60 b/b
62 A c.copy
61 A c.copy
63 c
62 c
64 R b/b
63 R b/b
65
64
66 prevent some foot-shooting
65 prevent some foot-shooting
67
66
68 $ hg shelve -n foo/bar
67 $ hg shelve -n foo/bar
69 abort: shelved change names may not contain slashes
68 abort: shelved change names may not contain slashes
70 [255]
69 [255]
71 $ hg shelve -n .baz
70 $ hg shelve -n .baz
72 abort: shelved change names may not start with '.'
71 abort: shelved change names may not start with '.'
73 [255]
72 [255]
74
73
75 the common case - no options or filenames
74 the common case - no options or filenames
76
75
77 $ hg shelve
76 $ hg shelve
78 shelved as default-01
77 shelved as default-01
79 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
78 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
80 $ hg status -C
79 $ hg status -C
81
80
82 ensure that our shelved changes exist
81 ensure that our shelved changes exist
83
82
84 $ hg shelve -l
83 $ hg shelve -l
85 default-01 (*) [mq]: second.patch (glob)
84 default-01 (*) [mq]: second.patch (glob)
86 default (*) [mq]: second.patch (glob)
85 default (*) [mq]: second.patch (glob)
87
86
88 $ hg shelve -l -p default
87 $ hg shelve -l -p default
89 default (*) [mq]: second.patch (glob)
88 default (*) [mq]: second.patch (glob)
90
89
91 diff --git a/a/a b/a/a
90 diff --git a/a/a b/a/a
92 --- a/a/a
91 --- a/a/a
93 +++ b/a/a
92 +++ b/a/a
94 @@ -1,1 +1,2 @@
93 @@ -1,1 +1,2 @@
95 a
94 a
96 +a
95 +a
97
96
98 delete our older shelved change
97 delete our older shelved change
99
98
100 $ hg shelve -d default
99 $ hg shelve -d default
101 $ hg qfinish -a -q
100 $ hg qfinish -a -q
102
101
103 local edits should prevent a shelved change from applying
102 local edits should not prevent a shelved change from applying
104
103
105 $ echo e>>a/a
104 $ printf "z\na\n" > a/a
106 $ hg unshelve
105 $ hg unshelve --keep
107 unshelving change 'default-01'
106 unshelving change 'default-01'
108 the following shelved files have been modified:
107 adding changesets
109 a/a
108 adding manifests
110 you must commit, revert, or shelve your changes before you can proceed
109 adding file changes
111 abort: cannot unshelve due to local changes
110 added 1 changesets with 3 changes to 8 files (+1 heads)
112
111 merging a/a
113 [255]
114
112
115 $ hg revert -C a/a
113 $ hg revert --all -q
114 $ rm a/a.orig b.rename/b c.copy
116
115
117 apply it and make sure our state is as expected
116 apply it and make sure our state is as expected
118
117
119 $ hg unshelve
118 $ hg unshelve
120 unshelving change 'default-01'
119 unshelving change 'default-01'
121 adding changesets
120 adding changesets
122 adding manifests
121 adding manifests
123 adding file changes
122 adding file changes
124 added 1 changesets with 3 changes to 8 files
123 added 1 changesets with 3 changes to 8 files
125 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
126 $ hg status -C
124 $ hg status -C
127 M a/a
125 M a/a
128 A b.rename/b
126 A b.rename/b
129 b/b
127 b/b
130 A c.copy
128 A c.copy
131 c
129 c
132 R b/b
130 R b/b
133 $ hg shelve -l
131 $ hg shelve -l
134
132
135 $ hg unshelve
133 $ hg unshelve
136 abort: no shelved changes to apply!
134 abort: no shelved changes to apply!
137 [255]
135 [255]
138 $ hg unshelve foo
136 $ hg unshelve foo
139 abort: shelved change 'foo' not found
137 abort: shelved change 'foo' not found
140 [255]
138 [255]
141
139
142 named shelves, specific filenames, and "commit messages" should all work
140 named shelves, specific filenames, and "commit messages" should all work
143
141
144 $ hg status -C
142 $ hg status -C
145 M a/a
143 M a/a
146 A b.rename/b
144 A b.rename/b
147 b/b
145 b/b
148 A c.copy
146 A c.copy
149 c
147 c
150 R b/b
148 R b/b
151 $ hg shelve -q -n wibble -m wat a
149 $ hg shelve -q -n wibble -m wat a
152
150
153 expect "a" to no longer be present, but status otherwise unchanged
151 expect "a" to no longer be present, but status otherwise unchanged
154
152
155 $ hg status -C
153 $ hg status -C
156 A b.rename/b
154 A b.rename/b
157 b/b
155 b/b
158 A c.copy
156 A c.copy
159 c
157 c
160 R b/b
158 R b/b
161 $ hg shelve -l --stat
159 $ hg shelve -l --stat
162 wibble (*) wat (glob)
160 wibble (*) wat (glob)
163 a/a | 1 +
161 a/a | 1 +
164 1 files changed, 1 insertions(+), 0 deletions(-)
162 1 files changed, 1 insertions(+), 0 deletions(-)
165
163
166 and now "a/a" should reappear
164 and now "a/a" should reappear
167
165
168 $ cd a
166 $ cd a
169 $ hg unshelve -q wibble
167 $ hg unshelve -q wibble
170 $ cd ..
168 $ cd ..
171 $ hg status -C
169 $ hg status -C
172 M a/a
170 M a/a
173 A b.rename/b
171 A b.rename/b
174 b/b
172 b/b
175 A c.copy
173 A c.copy
176 c
174 c
177 R b/b
175 R b/b
178
176
179 cause unshelving to result in a merge with 'a' conflicting
177 cause unshelving to result in a merge with 'a' conflicting
180
178
181 $ hg shelve -q
179 $ hg shelve -q
182 $ echo c>>a/a
180 $ echo c>>a/a
183 $ hg commit -m second
181 $ hg commit -m second
184 $ hg tip --template '{files}\n'
182 $ hg tip --template '{files}\n'
185 a/a
183 a/a
186
184
187 add an unrelated change that should be preserved
185 add an unrelated change that should be preserved
188
186
189 $ mkdir foo
187 $ mkdir foo
190 $ echo foo > foo/foo
188 $ echo foo > foo/foo
191 $ hg add foo/foo
189 $ hg add foo/foo
192
190
193 force a conflicted merge to occur
191 force a conflicted merge to occur
194
192
195 $ hg unshelve
193 $ hg unshelve
196 unshelving change 'default'
194 unshelving change 'default'
197 adding changesets
195 adding changesets
198 adding manifests
196 adding manifests
199 adding file changes
197 adding file changes
200 added 1 changesets with 3 changes to 8 files (+1 heads)
198 added 1 changesets with 3 changes to 8 files (+1 heads)
201 merging a/a
199 merging a/a
202 warning: conflicts during merge.
200 warning: conflicts during merge.
203 merging a/a incomplete! (edit conflicts, then use 'hg resolve --mark')
201 merging a/a incomplete! (edit conflicts, then use 'hg resolve --mark')
204 2 files updated, 0 files merged, 1 files removed, 1 files unresolved
205 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
206 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
202 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
207 [1]
203 [1]
208
204
209 ensure that we have a merge with unresolved conflicts
205 ensure that we have a merge with unresolved conflicts
210
206
211 $ hg heads -q
207 $ hg heads -q --template '{rev}\n'
212 4:cebf2b8de087
208 5
213 3:2e69b451d1ea
209 4
214 $ hg parents -q
210 $ hg parents -q --template '{rev}\n'
215 3:2e69b451d1ea
211 4
216 4:cebf2b8de087
212 5
217 $ hg status
213 $ hg status
218 M a/a
214 M a/a
219 M b.rename/b
215 M b.rename/b
220 M c.copy
216 M c.copy
221 A foo/foo
222 R b/b
217 R b/b
223 ? a/a.orig
218 ? a/a.orig
224 $ hg diff
219 $ hg diff
225 diff --git a/a/a b/a/a
220 diff --git a/a/a b/a/a
226 --- a/a/a
221 --- a/a/a
227 +++ b/a/a
222 +++ b/a/a
228 @@ -1,2 +1,6 @@
223 @@ -1,2 +1,6 @@
229 a
224 a
230 +<<<<<<< local
225 +<<<<<<< local
231 c
226 c
232 +=======
227 +=======
233 +a
228 +a
234 +>>>>>>> other
229 +>>>>>>> other
235 diff --git a/b.rename/b b/b.rename/b
230 diff --git a/b.rename/b b/b.rename/b
236 --- /dev/null
231 --- /dev/null
237 +++ b/b.rename/b
232 +++ b/b.rename/b
238 @@ -0,0 +1,1 @@
233 @@ -0,0 +1,1 @@
239 +b
234 +b
240 diff --git a/b/b b/b/b
235 diff --git a/b/b b/b/b
241 deleted file mode 100644
236 deleted file mode 100644
242 --- a/b/b
237 --- a/b/b
243 +++ /dev/null
238 +++ /dev/null
244 @@ -1,1 +0,0 @@
239 @@ -1,1 +0,0 @@
245 -b
240 -b
246 diff --git a/c.copy b/c.copy
241 diff --git a/c.copy b/c.copy
247 --- /dev/null
242 --- /dev/null
248 +++ b/c.copy
243 +++ b/c.copy
249 @@ -0,0 +1,1 @@
244 @@ -0,0 +1,1 @@
250 +c
245 +c
251 diff --git a/foo/foo b/foo/foo
252 new file mode 100644
253 --- /dev/null
254 +++ b/foo/foo
255 @@ -0,0 +1,1 @@
256 +foo
257 $ hg resolve -l
246 $ hg resolve -l
258 U a/a
247 U a/a
259
248
260 $ hg shelve
249 $ hg shelve
261 abort: unshelve already in progress
250 abort: unshelve already in progress
262 (use 'hg unshelve --continue' or 'hg unshelve --abort')
251 (use 'hg unshelve --continue' or 'hg unshelve --abort')
263 [255]
252 [255]
264
253
265 abort the unshelve and be happy
254 abort the unshelve and be happy
266
255
267 $ hg status
256 $ hg status
268 M a/a
257 M a/a
269 M b.rename/b
258 M b.rename/b
270 M c.copy
259 M c.copy
271 A foo/foo
272 R b/b
260 R b/b
273 ? a/a.orig
261 ? a/a.orig
274 $ hg unshelve -a
262 $ hg unshelve -a
263 rebase aborted
275 unshelve of 'default' aborted
264 unshelve of 'default' aborted
276 $ hg heads -q
265 $ hg heads -q
277 3:2e69b451d1ea
266 3:2e69b451d1ea
278 $ hg parents
267 $ hg parents
279 changeset: 3:2e69b451d1ea
268 changeset: 3:2e69b451d1ea
280 tag: tip
269 tag: tip
281 user: test
270 user: test
282 date: Thu Jan 01 00:00:00 1970 +0000
271 date: Thu Jan 01 00:00:00 1970 +0000
283 summary: second
272 summary: second
284
273
285 $ hg resolve -l
274 $ hg resolve -l
286 $ hg status
275 $ hg status
287 A foo/foo
276 A foo/foo
288 ? a/a.orig
277 ? a/a.orig
289
278
290 try to continue with no unshelve underway
279 try to continue with no unshelve underway
291
280
292 $ hg unshelve -c
281 $ hg unshelve -c
293 abort: no unshelve operation underway
282 abort: no unshelve operation underway
294 [255]
283 [255]
295 $ hg status
284 $ hg status
296 A foo/foo
285 A foo/foo
297 ? a/a.orig
286 ? a/a.orig
298
287
299 redo the unshelve to get a conflict
288 redo the unshelve to get a conflict
300
289
301 $ hg unshelve -q
290 $ hg unshelve -q
302 warning: conflicts during merge.
291 warning: conflicts during merge.
303 merging a/a incomplete! (edit conflicts, then use 'hg resolve --mark')
292 merging a/a incomplete! (edit conflicts, then use 'hg resolve --mark')
304 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
293 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
305 [1]
294 [1]
306
295
307 attempt to continue
296 attempt to continue
308
297
309 $ hg unshelve -c
298 $ hg unshelve -c
310 abort: unresolved conflicts, can't continue
299 abort: unresolved conflicts, can't continue
311 (see 'hg resolve', then 'hg unshelve --continue')
300 (see 'hg resolve', then 'hg unshelve --continue')
312 [255]
301 [255]
313
302
314 $ hg revert -r . a/a
303 $ hg revert -r . a/a
315 $ hg resolve -m a/a
304 $ hg resolve -m a/a
316
305
317 $ hg unshelve -c
306 $ hg unshelve -c
318 unshelve of 'default' complete
307 unshelve of 'default' complete
319
308
320 ensure the repo is as we hope
309 ensure the repo is as we hope
321
310
322 $ hg parents
311 $ hg parents
323 changeset: 3:2e69b451d1ea
312 changeset: 3:2e69b451d1ea
324 tag: tip
313 tag: tip
325 user: test
314 user: test
326 date: Thu Jan 01 00:00:00 1970 +0000
315 date: Thu Jan 01 00:00:00 1970 +0000
327 summary: second
316 summary: second
328
317
329 $ hg heads -q
318 $ hg heads -q
330 3:2e69b451d1ea
319 3:2e69b451d1ea
331
320
332 $ hg status -C
321 $ hg status -C
333 M b.rename/b
322 A b.rename/b
334 b/b
323 b/b
335 M c.copy
324 A c.copy
336 c
325 c
337 A foo/foo
326 A foo/foo
338 R b/b
327 R b/b
339 ? a/a.orig
328 ? a/a.orig
340
329
341 there should be no shelves left
330 there should be no shelves left
342
331
343 $ hg shelve -l
332 $ hg shelve -l
344
333
345 #if execbit
334 #if execbit
346
335
347 ensure that metadata-only changes are shelved
336 ensure that metadata-only changes are shelved
348
337
349 $ chmod +x a/a
338 $ chmod +x a/a
350 $ hg shelve -q -n execbit a/a
339 $ hg shelve -q -n execbit a/a
351 $ hg status a/a
340 $ hg status a/a
352 $ hg unshelve -q execbit
341 $ hg unshelve -q execbit
353 $ hg status a/a
342 $ hg status a/a
354 M a/a
343 M a/a
355 $ hg revert a/a
344 $ hg revert a/a
356
345
357 #endif
346 #endif
358
347
359 #if symlink
348 #if symlink
360
349
361 $ rm a/a
350 $ rm a/a
362 $ ln -s foo a/a
351 $ ln -s foo a/a
363 $ hg shelve -q -n symlink a/a
352 $ hg shelve -q -n symlink a/a
364 $ hg status a/a
353 $ hg status a/a
365 $ hg unshelve -q symlink
354 $ hg unshelve -q symlink
366 $ hg status a/a
355 $ hg status a/a
367 M a/a
356 M a/a
368 $ hg revert a/a
357 $ hg revert a/a
369
358
370 #endif
359 #endif
371
360
372 set up another conflict between a commit and a shelved change
361 set up another conflict between a commit and a shelved change
373
362
374 $ hg revert -q -C -a
363 $ hg revert -q -C -a
364 $ rm a/a.orig b.rename/b c.copy
375 $ echo a >> a/a
365 $ echo a >> a/a
376 $ hg shelve -q
366 $ hg shelve -q
377 $ echo x >> a/a
367 $ echo x >> a/a
378 $ hg ci -m 'create conflict'
368 $ hg ci -m 'create conflict'
379 $ hg add foo/foo
369 $ hg add foo/foo
380
370
381 if we resolve a conflict while unshelving, the unshelve should succeed
371 if we resolve a conflict while unshelving, the unshelve should succeed
382
372
383 $ HGMERGE=true hg unshelve
373 $ HGMERGE=true hg unshelve
384 unshelving change 'default'
374 unshelving change 'default'
385 adding changesets
375 adding changesets
386 adding manifests
376 adding manifests
387 adding file changes
377 adding file changes
388 added 1 changesets with 1 changes to 6 files (+1 heads)
378 added 1 changesets with 1 changes to 6 files (+1 heads)
389 merging a/a
379 merging a/a
390 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
391 $ hg parents -q
380 $ hg parents -q
392 4:33f7f61e6c5e
381 4:33f7f61e6c5e
393 $ hg shelve -l
382 $ hg shelve -l
394 $ hg status
383 $ hg status
395 A foo/foo
384 A foo/foo
396 $ cat a/a
385 $ cat a/a
397 a
386 a
398 c
387 c
399 x
388 x
400
389
401 test keep and cleanup
390 test keep and cleanup
402
391
403 $ hg shelve
392 $ hg shelve
404 shelved as default
393 shelved as default
405 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
394 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
406 $ hg shelve --list
395 $ hg shelve --list
407 default (*) create conflict (glob)
396 default (*) create conflict (glob)
408 $ hg unshelve --keep
397 $ hg unshelve --keep
409 unshelving change 'default'
398 unshelving change 'default'
410 adding changesets
399 adding changesets
411 adding manifests
400 adding manifests
412 adding file changes
401 adding file changes
413 added 1 changesets with 1 changes to 7 files
402 added 1 changesets with 1 changes to 7 files
414 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
415 $ hg shelve --list
403 $ hg shelve --list
416 default (*) create conflict (glob)
404 default (*) create conflict (glob)
417 $ hg shelve --cleanup
405 $ hg shelve --cleanup
418 $ hg shelve --list
406 $ hg shelve --list
419
407
420 test bookmarks
408 test bookmarks
421
409
422 $ hg bookmark test
410 $ hg bookmark test
423 $ hg bookmark
411 $ hg bookmark
424 * test 4:33f7f61e6c5e
412 * test 4:33f7f61e6c5e
425 $ hg shelve
413 $ hg shelve
426 shelved as test
414 shelved as test
427 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
415 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
428 $ hg bookmark
416 $ hg bookmark
429 * test 4:33f7f61e6c5e
417 * test 4:33f7f61e6c5e
430 $ hg unshelve
418 $ hg unshelve
431 unshelving change 'test'
419 unshelving change 'test'
432 adding changesets
420 adding changesets
433 adding manifests
421 adding manifests
434 adding file changes
422 adding file changes
435 added 1 changesets with 1 changes to 7 files
423 added 1 changesets with 1 changes to 7 files
436 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
437 $ hg bookmark
424 $ hg bookmark
438 * test 4:33f7f61e6c5e
425 * test 4:33f7f61e6c5e
439
426
440 shelve should still work even if mq is disabled
427 shelve should still work even if mq is disabled
441
428
442 $ hg --config extensions.mq=! shelve
429 $ hg --config extensions.mq=! shelve
443 shelved as test
430 shelved as test
444 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
431 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
445 $ hg --config extensions.mq=! shelve --list
432 $ hg --config extensions.mq=! shelve --list
446 test (1s ago) create conflict
433 test (1s ago) create conflict
447 $ hg --config extensions.mq=! unshelve
434 $ hg --config extensions.mq=! unshelve
448 unshelving change 'test'
435 unshelving change 'test'
449 adding changesets
436 adding changesets
450 adding manifests
437 adding manifests
451 adding file changes
438 adding file changes
452 added 1 changesets with 1 changes to 7 files
439 added 1 changesets with 1 changes to 7 files
453 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
454
440
455 shelve should leave dirstate clean (issue 4055)
441 shelve should leave dirstate clean (issue 4055)
456
442
457 $ cd ..
443 $ cd ..
458 $ hg init shelverebase
444 $ hg init shelverebase
459 $ cd shelverebase
445 $ cd shelverebase
460 $ printf 'x\ny\n' > x
446 $ printf 'x\ny\n' > x
461 $ echo z > z
447 $ echo z > z
462 $ hg commit -Aqm xy
448 $ hg commit -Aqm xy
463 $ echo z >> x
449 $ echo z >> x
464 $ hg commit -Aqm z
450 $ hg commit -Aqm z
465 $ hg up 0
451 $ hg up 0
466 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
452 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
467 $ printf 'a\nx\ny\nz\n' > x
453 $ printf 'a\nx\ny\nz\n' > x
468 $ hg commit -Aqm xyz
454 $ hg commit -Aqm xyz
469 $ echo c >> z
455 $ echo c >> z
470 $ hg shelve
456 $ hg shelve
471 shelved as default
457 shelved as default
472 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
458 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
473 $ hg rebase -d 1 --config extensions.rebase=
459 $ hg rebase -d 1 --config extensions.rebase=
474 merging x
460 merging x
475 saved backup bundle to $TESTTMP/shelverebase/.hg/strip-backup/323bfa07f744-backup.hg (glob)
461 saved backup bundle to $TESTTMP/shelverebase/.hg/strip-backup/323bfa07f744-backup.hg (glob)
476 $ hg unshelve
462 $ hg unshelve
477 unshelving change 'default'
463 unshelving change 'default'
478 adding changesets
464 adding changesets
479 adding manifests
465 adding manifests
480 adding file changes
466 adding file changes
481 added 2 changesets with 2 changes to 2 files (+1 heads)
467 added 2 changesets with 2 changes to 2 files (+1 heads)
482 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
483 $ hg status
468 $ hg status
484 M z
469 M z
485
470
486 $ cd ..
471 $ cd ..
472
473 shelve should only unshelve pending changes (issue 4068)
474
475 $ hg init onlypendingchanges
476 $ cd onlypendingchanges
477 $ touch a
478 $ hg ci -Aqm a
479 $ touch b
480 $ hg ci -Aqm b
481 $ hg up -q 0
482 $ touch c
483 $ hg ci -Aqm c
484
485 $ touch d
486 $ hg add d
487 $ hg shelve
488 shelved as default
489 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
490 $ hg up -q 1
491 $ hg unshelve
492 unshelving change 'default'
493 adding changesets
494 adding manifests
495 adding file changes
496 added 1 changesets with 1 changes to 3 files
497 $ hg status
498 A d
499
500 unshelve should work on an ancestor of the original commit
501
502 $ hg shelve
503 shelved as default
504 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
505 $ hg up 0
506 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
507 $ hg unshelve
508 unshelving change 'default'
509 adding changesets
510 adding manifests
511 adding file changes
512 added 1 changesets with 1 changes to 3 files
513 $ hg status
514 A d
515
516 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now