##// END OF EJS Templates
largefiles: ensure lfutil.getstandinmatcher() only matches standins...
Matt Harbison -
r26025:ba808943 stable
parent child Browse files
Show More
@@ -1,610 +1,612
1 # Copyright 2009-2010 Gregory P. Ward
1 # Copyright 2009-2010 Gregory P. Ward
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 # Copyright 2010-2011 Fog Creek Software
3 # Copyright 2010-2011 Fog Creek Software
4 # Copyright 2010-2011 Unity Technologies
4 # Copyright 2010-2011 Unity Technologies
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 '''largefiles utility code: must not import other modules in this package.'''
9 '''largefiles utility code: must not import other modules in this package.'''
10
10
11 import os
11 import os
12 import platform
12 import platform
13 import shutil
13 import shutil
14 import stat
14 import stat
15 import copy
15 import copy
16
16
17 from mercurial import dirstate, httpconnection, match as match_, util, scmutil
17 from mercurial import dirstate, httpconnection, match as match_, util, scmutil
18 from mercurial.i18n import _
18 from mercurial.i18n import _
19 from mercurial import node
19 from mercurial import node
20
20
21 shortname = '.hglf'
21 shortname = '.hglf'
22 shortnameslash = shortname + '/'
22 shortnameslash = shortname + '/'
23 longname = 'largefiles'
23 longname = 'largefiles'
24
24
25
25
26 # -- Private worker functions ------------------------------------------
26 # -- Private worker functions ------------------------------------------
27
27
28 def getminsize(ui, assumelfiles, opt, default=10):
28 def getminsize(ui, assumelfiles, opt, default=10):
29 lfsize = opt
29 lfsize = opt
30 if not lfsize and assumelfiles:
30 if not lfsize and assumelfiles:
31 lfsize = ui.config(longname, 'minsize', default=default)
31 lfsize = ui.config(longname, 'minsize', default=default)
32 if lfsize:
32 if lfsize:
33 try:
33 try:
34 lfsize = float(lfsize)
34 lfsize = float(lfsize)
35 except ValueError:
35 except ValueError:
36 raise util.Abort(_('largefiles: size must be number (not %s)\n')
36 raise util.Abort(_('largefiles: size must be number (not %s)\n')
37 % lfsize)
37 % lfsize)
38 if lfsize is None:
38 if lfsize is None:
39 raise util.Abort(_('minimum size for largefiles must be specified'))
39 raise util.Abort(_('minimum size for largefiles must be specified'))
40 return lfsize
40 return lfsize
41
41
42 def link(src, dest):
42 def link(src, dest):
43 util.makedirs(os.path.dirname(dest))
43 util.makedirs(os.path.dirname(dest))
44 try:
44 try:
45 util.oslink(src, dest)
45 util.oslink(src, dest)
46 except OSError:
46 except OSError:
47 # if hardlinks fail, fallback on atomic copy
47 # if hardlinks fail, fallback on atomic copy
48 dst = util.atomictempfile(dest)
48 dst = util.atomictempfile(dest)
49 for chunk in util.filechunkiter(open(src, 'rb')):
49 for chunk in util.filechunkiter(open(src, 'rb')):
50 dst.write(chunk)
50 dst.write(chunk)
51 dst.close()
51 dst.close()
52 os.chmod(dest, os.stat(src).st_mode)
52 os.chmod(dest, os.stat(src).st_mode)
53
53
54 def usercachepath(ui, hash):
54 def usercachepath(ui, hash):
55 path = ui.configpath(longname, 'usercache', None)
55 path = ui.configpath(longname, 'usercache', None)
56 if path:
56 if path:
57 path = os.path.join(path, hash)
57 path = os.path.join(path, hash)
58 else:
58 else:
59 if os.name == 'nt':
59 if os.name == 'nt':
60 appdata = os.getenv('LOCALAPPDATA', os.getenv('APPDATA'))
60 appdata = os.getenv('LOCALAPPDATA', os.getenv('APPDATA'))
61 if appdata:
61 if appdata:
62 path = os.path.join(appdata, longname, hash)
62 path = os.path.join(appdata, longname, hash)
63 elif platform.system() == 'Darwin':
63 elif platform.system() == 'Darwin':
64 home = os.getenv('HOME')
64 home = os.getenv('HOME')
65 if home:
65 if home:
66 path = os.path.join(home, 'Library', 'Caches',
66 path = os.path.join(home, 'Library', 'Caches',
67 longname, hash)
67 longname, hash)
68 elif os.name == 'posix':
68 elif os.name == 'posix':
69 path = os.getenv('XDG_CACHE_HOME')
69 path = os.getenv('XDG_CACHE_HOME')
70 if path:
70 if path:
71 path = os.path.join(path, longname, hash)
71 path = os.path.join(path, longname, hash)
72 else:
72 else:
73 home = os.getenv('HOME')
73 home = os.getenv('HOME')
74 if home:
74 if home:
75 path = os.path.join(home, '.cache', longname, hash)
75 path = os.path.join(home, '.cache', longname, hash)
76 else:
76 else:
77 raise util.Abort(_('unknown operating system: %s\n') % os.name)
77 raise util.Abort(_('unknown operating system: %s\n') % os.name)
78 return path
78 return path
79
79
80 def inusercache(ui, hash):
80 def inusercache(ui, hash):
81 path = usercachepath(ui, hash)
81 path = usercachepath(ui, hash)
82 return path and os.path.exists(path)
82 return path and os.path.exists(path)
83
83
84 def findfile(repo, hash):
84 def findfile(repo, hash):
85 path, exists = findstorepath(repo, hash)
85 path, exists = findstorepath(repo, hash)
86 if exists:
86 if exists:
87 repo.ui.note(_('found %s in store\n') % hash)
87 repo.ui.note(_('found %s in store\n') % hash)
88 return path
88 return path
89 elif inusercache(repo.ui, hash):
89 elif inusercache(repo.ui, hash):
90 repo.ui.note(_('found %s in system cache\n') % hash)
90 repo.ui.note(_('found %s in system cache\n') % hash)
91 path = storepath(repo, hash)
91 path = storepath(repo, hash)
92 link(usercachepath(repo.ui, hash), path)
92 link(usercachepath(repo.ui, hash), path)
93 return path
93 return path
94 return None
94 return None
95
95
96 class largefilesdirstate(dirstate.dirstate):
96 class largefilesdirstate(dirstate.dirstate):
97 def __getitem__(self, key):
97 def __getitem__(self, key):
98 return super(largefilesdirstate, self).__getitem__(unixpath(key))
98 return super(largefilesdirstate, self).__getitem__(unixpath(key))
99 def normal(self, f):
99 def normal(self, f):
100 return super(largefilesdirstate, self).normal(unixpath(f))
100 return super(largefilesdirstate, self).normal(unixpath(f))
101 def remove(self, f):
101 def remove(self, f):
102 return super(largefilesdirstate, self).remove(unixpath(f))
102 return super(largefilesdirstate, self).remove(unixpath(f))
103 def add(self, f):
103 def add(self, f):
104 return super(largefilesdirstate, self).add(unixpath(f))
104 return super(largefilesdirstate, self).add(unixpath(f))
105 def drop(self, f):
105 def drop(self, f):
106 return super(largefilesdirstate, self).drop(unixpath(f))
106 return super(largefilesdirstate, self).drop(unixpath(f))
107 def forget(self, f):
107 def forget(self, f):
108 return super(largefilesdirstate, self).forget(unixpath(f))
108 return super(largefilesdirstate, self).forget(unixpath(f))
109 def normallookup(self, f):
109 def normallookup(self, f):
110 return super(largefilesdirstate, self).normallookup(unixpath(f))
110 return super(largefilesdirstate, self).normallookup(unixpath(f))
111 def _ignore(self, f):
111 def _ignore(self, f):
112 return False
112 return False
113
113
114 def openlfdirstate(ui, repo, create=True):
114 def openlfdirstate(ui, repo, create=True):
115 '''
115 '''
116 Return a dirstate object that tracks largefiles: i.e. its root is
116 Return a dirstate object that tracks largefiles: i.e. its root is
117 the repo root, but it is saved in .hg/largefiles/dirstate.
117 the repo root, but it is saved in .hg/largefiles/dirstate.
118 '''
118 '''
119 lfstoredir = repo.join(longname)
119 lfstoredir = repo.join(longname)
120 opener = scmutil.opener(lfstoredir)
120 opener = scmutil.opener(lfstoredir)
121 lfdirstate = largefilesdirstate(opener, ui, repo.root,
121 lfdirstate = largefilesdirstate(opener, ui, repo.root,
122 repo.dirstate._validate)
122 repo.dirstate._validate)
123
123
124 # If the largefiles dirstate does not exist, populate and create
124 # If the largefiles dirstate does not exist, populate and create
125 # it. This ensures that we create it on the first meaningful
125 # it. This ensures that we create it on the first meaningful
126 # largefiles operation in a new clone.
126 # largefiles operation in a new clone.
127 if create and not os.path.exists(os.path.join(lfstoredir, 'dirstate')):
127 if create and not os.path.exists(os.path.join(lfstoredir, 'dirstate')):
128 matcher = getstandinmatcher(repo)
128 matcher = getstandinmatcher(repo)
129 standins = repo.dirstate.walk(matcher, [], False, False)
129 standins = repo.dirstate.walk(matcher, [], False, False)
130
130
131 if len(standins) > 0:
131 if len(standins) > 0:
132 util.makedirs(lfstoredir)
132 util.makedirs(lfstoredir)
133
133
134 for standin in standins:
134 for standin in standins:
135 lfile = splitstandin(standin)
135 lfile = splitstandin(standin)
136 lfdirstate.normallookup(lfile)
136 lfdirstate.normallookup(lfile)
137 return lfdirstate
137 return lfdirstate
138
138
139 def lfdirstatestatus(lfdirstate, repo):
139 def lfdirstatestatus(lfdirstate, repo):
140 wctx = repo['.']
140 wctx = repo['.']
141 match = match_.always(repo.root, repo.getcwd())
141 match = match_.always(repo.root, repo.getcwd())
142 unsure, s = lfdirstate.status(match, [], False, False, False)
142 unsure, s = lfdirstate.status(match, [], False, False, False)
143 modified, clean = s.modified, s.clean
143 modified, clean = s.modified, s.clean
144 for lfile in unsure:
144 for lfile in unsure:
145 try:
145 try:
146 fctx = wctx[standin(lfile)]
146 fctx = wctx[standin(lfile)]
147 except LookupError:
147 except LookupError:
148 fctx = None
148 fctx = None
149 if not fctx or fctx.data().strip() != hashfile(repo.wjoin(lfile)):
149 if not fctx or fctx.data().strip() != hashfile(repo.wjoin(lfile)):
150 modified.append(lfile)
150 modified.append(lfile)
151 else:
151 else:
152 clean.append(lfile)
152 clean.append(lfile)
153 lfdirstate.normal(lfile)
153 lfdirstate.normal(lfile)
154 return s
154 return s
155
155
156 def listlfiles(repo, rev=None, matcher=None):
156 def listlfiles(repo, rev=None, matcher=None):
157 '''return a list of largefiles in the working copy or the
157 '''return a list of largefiles in the working copy or the
158 specified changeset'''
158 specified changeset'''
159
159
160 if matcher is None:
160 if matcher is None:
161 matcher = getstandinmatcher(repo)
161 matcher = getstandinmatcher(repo)
162
162
163 # ignore unknown files in working directory
163 # ignore unknown files in working directory
164 return [splitstandin(f)
164 return [splitstandin(f)
165 for f in repo[rev].walk(matcher)
165 for f in repo[rev].walk(matcher)
166 if rev is not None or repo.dirstate[f] != '?']
166 if rev is not None or repo.dirstate[f] != '?']
167
167
168 def instore(repo, hash, forcelocal=False):
168 def instore(repo, hash, forcelocal=False):
169 return os.path.exists(storepath(repo, hash, forcelocal))
169 return os.path.exists(storepath(repo, hash, forcelocal))
170
170
171 def storepath(repo, hash, forcelocal=False):
171 def storepath(repo, hash, forcelocal=False):
172 if not forcelocal and repo.shared():
172 if not forcelocal and repo.shared():
173 return repo.vfs.reljoin(repo.sharedpath, longname, hash)
173 return repo.vfs.reljoin(repo.sharedpath, longname, hash)
174 return repo.join(longname, hash)
174 return repo.join(longname, hash)
175
175
176 def findstorepath(repo, hash):
176 def findstorepath(repo, hash):
177 '''Search through the local store path(s) to find the file for the given
177 '''Search through the local store path(s) to find the file for the given
178 hash. If the file is not found, its path in the primary store is returned.
178 hash. If the file is not found, its path in the primary store is returned.
179 The return value is a tuple of (path, exists(path)).
179 The return value is a tuple of (path, exists(path)).
180 '''
180 '''
181 # For shared repos, the primary store is in the share source. But for
181 # For shared repos, the primary store is in the share source. But for
182 # backward compatibility, force a lookup in the local store if it wasn't
182 # backward compatibility, force a lookup in the local store if it wasn't
183 # found in the share source.
183 # found in the share source.
184 path = storepath(repo, hash, False)
184 path = storepath(repo, hash, False)
185
185
186 if instore(repo, hash):
186 if instore(repo, hash):
187 return (path, True)
187 return (path, True)
188 elif repo.shared() and instore(repo, hash, True):
188 elif repo.shared() and instore(repo, hash, True):
189 return storepath(repo, hash, True)
189 return storepath(repo, hash, True)
190
190
191 return (path, False)
191 return (path, False)
192
192
193 def copyfromcache(repo, hash, filename):
193 def copyfromcache(repo, hash, filename):
194 '''Copy the specified largefile from the repo or system cache to
194 '''Copy the specified largefile from the repo or system cache to
195 filename in the repository. Return true on success or false if the
195 filename in the repository. Return true on success or false if the
196 file was not found in either cache (which should not happened:
196 file was not found in either cache (which should not happened:
197 this is meant to be called only after ensuring that the needed
197 this is meant to be called only after ensuring that the needed
198 largefile exists in the cache).'''
198 largefile exists in the cache).'''
199 path = findfile(repo, hash)
199 path = findfile(repo, hash)
200 if path is None:
200 if path is None:
201 return False
201 return False
202 util.makedirs(os.path.dirname(repo.wjoin(filename)))
202 util.makedirs(os.path.dirname(repo.wjoin(filename)))
203 # The write may fail before the file is fully written, but we
203 # The write may fail before the file is fully written, but we
204 # don't use atomic writes in the working copy.
204 # don't use atomic writes in the working copy.
205 shutil.copy(path, repo.wjoin(filename))
205 shutil.copy(path, repo.wjoin(filename))
206 return True
206 return True
207
207
208 def copytostore(repo, rev, file, uploaded=False):
208 def copytostore(repo, rev, file, uploaded=False):
209 hash = readstandin(repo, file, rev)
209 hash = readstandin(repo, file, rev)
210 if instore(repo, hash):
210 if instore(repo, hash):
211 return
211 return
212 copytostoreabsolute(repo, repo.wjoin(file), hash)
212 copytostoreabsolute(repo, repo.wjoin(file), hash)
213
213
214 def copyalltostore(repo, node):
214 def copyalltostore(repo, node):
215 '''Copy all largefiles in a given revision to the store'''
215 '''Copy all largefiles in a given revision to the store'''
216
216
217 ctx = repo[node]
217 ctx = repo[node]
218 for filename in ctx.files():
218 for filename in ctx.files():
219 if isstandin(filename) and filename in ctx.manifest():
219 if isstandin(filename) and filename in ctx.manifest():
220 realfile = splitstandin(filename)
220 realfile = splitstandin(filename)
221 copytostore(repo, ctx.node(), realfile)
221 copytostore(repo, ctx.node(), realfile)
222
222
223
223
224 def copytostoreabsolute(repo, file, hash):
224 def copytostoreabsolute(repo, file, hash):
225 if inusercache(repo.ui, hash):
225 if inusercache(repo.ui, hash):
226 link(usercachepath(repo.ui, hash), storepath(repo, hash))
226 link(usercachepath(repo.ui, hash), storepath(repo, hash))
227 else:
227 else:
228 util.makedirs(os.path.dirname(storepath(repo, hash)))
228 util.makedirs(os.path.dirname(storepath(repo, hash)))
229 dst = util.atomictempfile(storepath(repo, hash),
229 dst = util.atomictempfile(storepath(repo, hash),
230 createmode=repo.store.createmode)
230 createmode=repo.store.createmode)
231 for chunk in util.filechunkiter(open(file, 'rb')):
231 for chunk in util.filechunkiter(open(file, 'rb')):
232 dst.write(chunk)
232 dst.write(chunk)
233 dst.close()
233 dst.close()
234 linktousercache(repo, hash)
234 linktousercache(repo, hash)
235
235
236 def linktousercache(repo, hash):
236 def linktousercache(repo, hash):
237 path = usercachepath(repo.ui, hash)
237 path = usercachepath(repo.ui, hash)
238 if path:
238 if path:
239 link(storepath(repo, hash), path)
239 link(storepath(repo, hash), path)
240
240
241 def getstandinmatcher(repo, rmatcher=None):
241 def getstandinmatcher(repo, rmatcher=None):
242 '''Return a match object that applies rmatcher to the standin directory'''
242 '''Return a match object that applies rmatcher to the standin directory'''
243 standindir = repo.wjoin(shortname)
243 standindir = repo.wjoin(shortname)
244
244
245 # no warnings about missing files or directories
245 # no warnings about missing files or directories
246 badfn = lambda f, msg: None
246 badfn = lambda f, msg: None
247
247
248 if rmatcher and not rmatcher.always():
248 if rmatcher and not rmatcher.always():
249 pats = [os.path.join(standindir, pat) for pat in rmatcher.files()]
249 pats = [os.path.join(standindir, pat) for pat in rmatcher.files()]
250 if not pats:
251 pats = [standindir]
250 match = scmutil.match(repo[None], pats, badfn=badfn)
252 match = scmutil.match(repo[None], pats, badfn=badfn)
251 # if pats is empty, it would incorrectly always match, so clear _always
253 # if pats is empty, it would incorrectly always match, so clear _always
252 match._always = False
254 match._always = False
253 else:
255 else:
254 # no patterns: relative to repo root
256 # no patterns: relative to repo root
255 match = scmutil.match(repo[None], [standindir], badfn=badfn)
257 match = scmutil.match(repo[None], [standindir], badfn=badfn)
256 return match
258 return match
257
259
258 def composestandinmatcher(repo, rmatcher):
260 def composestandinmatcher(repo, rmatcher):
259 '''Return a matcher that accepts standins corresponding to the
261 '''Return a matcher that accepts standins corresponding to the
260 files accepted by rmatcher. Pass the list of files in the matcher
262 files accepted by rmatcher. Pass the list of files in the matcher
261 as the paths specified by the user.'''
263 as the paths specified by the user.'''
262 smatcher = getstandinmatcher(repo, rmatcher)
264 smatcher = getstandinmatcher(repo, rmatcher)
263 isstandin = smatcher.matchfn
265 isstandin = smatcher.matchfn
264 def composedmatchfn(f):
266 def composedmatchfn(f):
265 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
267 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
266 smatcher.matchfn = composedmatchfn
268 smatcher.matchfn = composedmatchfn
267
269
268 return smatcher
270 return smatcher
269
271
270 def standin(filename):
272 def standin(filename):
271 '''Return the repo-relative path to the standin for the specified big
273 '''Return the repo-relative path to the standin for the specified big
272 file.'''
274 file.'''
273 # Notes:
275 # Notes:
274 # 1) Some callers want an absolute path, but for instance addlargefiles
276 # 1) Some callers want an absolute path, but for instance addlargefiles
275 # needs it repo-relative so it can be passed to repo[None].add(). So
277 # needs it repo-relative so it can be passed to repo[None].add(). So
276 # leave it up to the caller to use repo.wjoin() to get an absolute path.
278 # leave it up to the caller to use repo.wjoin() to get an absolute path.
277 # 2) Join with '/' because that's what dirstate always uses, even on
279 # 2) Join with '/' because that's what dirstate always uses, even on
278 # Windows. Change existing separator to '/' first in case we are
280 # Windows. Change existing separator to '/' first in case we are
279 # passed filenames from an external source (like the command line).
281 # passed filenames from an external source (like the command line).
280 return shortnameslash + util.pconvert(filename)
282 return shortnameslash + util.pconvert(filename)
281
283
282 def isstandin(filename):
284 def isstandin(filename):
283 '''Return true if filename is a big file standin. filename must be
285 '''Return true if filename is a big file standin. filename must be
284 in Mercurial's internal form (slash-separated).'''
286 in Mercurial's internal form (slash-separated).'''
285 return filename.startswith(shortnameslash)
287 return filename.startswith(shortnameslash)
286
288
287 def splitstandin(filename):
289 def splitstandin(filename):
288 # Split on / because that's what dirstate always uses, even on Windows.
290 # Split on / because that's what dirstate always uses, even on Windows.
289 # Change local separator to / first just in case we are passed filenames
291 # Change local separator to / first just in case we are passed filenames
290 # from an external source (like the command line).
292 # from an external source (like the command line).
291 bits = util.pconvert(filename).split('/', 1)
293 bits = util.pconvert(filename).split('/', 1)
292 if len(bits) == 2 and bits[0] == shortname:
294 if len(bits) == 2 and bits[0] == shortname:
293 return bits[1]
295 return bits[1]
294 else:
296 else:
295 return None
297 return None
296
298
297 def updatestandin(repo, standin):
299 def updatestandin(repo, standin):
298 file = repo.wjoin(splitstandin(standin))
300 file = repo.wjoin(splitstandin(standin))
299 if os.path.exists(file):
301 if os.path.exists(file):
300 hash = hashfile(file)
302 hash = hashfile(file)
301 executable = getexecutable(file)
303 executable = getexecutable(file)
302 writestandin(repo, standin, hash, executable)
304 writestandin(repo, standin, hash, executable)
303
305
304 def readstandin(repo, filename, node=None):
306 def readstandin(repo, filename, node=None):
305 '''read hex hash from standin for filename at given node, or working
307 '''read hex hash from standin for filename at given node, or working
306 directory if no node is given'''
308 directory if no node is given'''
307 return repo[node][standin(filename)].data().strip()
309 return repo[node][standin(filename)].data().strip()
308
310
309 def writestandin(repo, standin, hash, executable):
311 def writestandin(repo, standin, hash, executable):
310 '''write hash to <repo.root>/<standin>'''
312 '''write hash to <repo.root>/<standin>'''
311 repo.wwrite(standin, hash + '\n', executable and 'x' or '')
313 repo.wwrite(standin, hash + '\n', executable and 'x' or '')
312
314
313 def copyandhash(instream, outfile):
315 def copyandhash(instream, outfile):
314 '''Read bytes from instream (iterable) and write them to outfile,
316 '''Read bytes from instream (iterable) and write them to outfile,
315 computing the SHA-1 hash of the data along the way. Return the hash.'''
317 computing the SHA-1 hash of the data along the way. Return the hash.'''
316 hasher = util.sha1('')
318 hasher = util.sha1('')
317 for data in instream:
319 for data in instream:
318 hasher.update(data)
320 hasher.update(data)
319 outfile.write(data)
321 outfile.write(data)
320 return hasher.hexdigest()
322 return hasher.hexdigest()
321
323
322 def hashrepofile(repo, file):
324 def hashrepofile(repo, file):
323 return hashfile(repo.wjoin(file))
325 return hashfile(repo.wjoin(file))
324
326
325 def hashfile(file):
327 def hashfile(file):
326 if not os.path.exists(file):
328 if not os.path.exists(file):
327 return ''
329 return ''
328 hasher = util.sha1('')
330 hasher = util.sha1('')
329 fd = open(file, 'rb')
331 fd = open(file, 'rb')
330 for data in util.filechunkiter(fd, 128 * 1024):
332 for data in util.filechunkiter(fd, 128 * 1024):
331 hasher.update(data)
333 hasher.update(data)
332 fd.close()
334 fd.close()
333 return hasher.hexdigest()
335 return hasher.hexdigest()
334
336
335 def getexecutable(filename):
337 def getexecutable(filename):
336 mode = os.stat(filename).st_mode
338 mode = os.stat(filename).st_mode
337 return ((mode & stat.S_IXUSR) and
339 return ((mode & stat.S_IXUSR) and
338 (mode & stat.S_IXGRP) and
340 (mode & stat.S_IXGRP) and
339 (mode & stat.S_IXOTH))
341 (mode & stat.S_IXOTH))
340
342
341 def urljoin(first, second, *arg):
343 def urljoin(first, second, *arg):
342 def join(left, right):
344 def join(left, right):
343 if not left.endswith('/'):
345 if not left.endswith('/'):
344 left += '/'
346 left += '/'
345 if right.startswith('/'):
347 if right.startswith('/'):
346 right = right[1:]
348 right = right[1:]
347 return left + right
349 return left + right
348
350
349 url = join(first, second)
351 url = join(first, second)
350 for a in arg:
352 for a in arg:
351 url = join(url, a)
353 url = join(url, a)
352 return url
354 return url
353
355
354 def hexsha1(data):
356 def hexsha1(data):
355 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
357 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
356 object data"""
358 object data"""
357 h = util.sha1()
359 h = util.sha1()
358 for chunk in util.filechunkiter(data):
360 for chunk in util.filechunkiter(data):
359 h.update(chunk)
361 h.update(chunk)
360 return h.hexdigest()
362 return h.hexdigest()
361
363
362 def httpsendfile(ui, filename):
364 def httpsendfile(ui, filename):
363 return httpconnection.httpsendfile(ui, filename, 'rb')
365 return httpconnection.httpsendfile(ui, filename, 'rb')
364
366
365 def unixpath(path):
367 def unixpath(path):
366 '''Return a version of path normalized for use with the lfdirstate.'''
368 '''Return a version of path normalized for use with the lfdirstate.'''
367 return util.pconvert(os.path.normpath(path))
369 return util.pconvert(os.path.normpath(path))
368
370
369 def islfilesrepo(repo):
371 def islfilesrepo(repo):
370 if ('largefiles' in repo.requirements and
372 if ('largefiles' in repo.requirements and
371 any(shortnameslash in f[0] for f in repo.store.datafiles())):
373 any(shortnameslash in f[0] for f in repo.store.datafiles())):
372 return True
374 return True
373
375
374 return any(openlfdirstate(repo.ui, repo, False))
376 return any(openlfdirstate(repo.ui, repo, False))
375
377
376 class storeprotonotcapable(Exception):
378 class storeprotonotcapable(Exception):
377 def __init__(self, storetypes):
379 def __init__(self, storetypes):
378 self.storetypes = storetypes
380 self.storetypes = storetypes
379
381
380 def getstandinsstate(repo):
382 def getstandinsstate(repo):
381 standins = []
383 standins = []
382 matcher = getstandinmatcher(repo)
384 matcher = getstandinmatcher(repo)
383 for standin in repo.dirstate.walk(matcher, [], False, False):
385 for standin in repo.dirstate.walk(matcher, [], False, False):
384 lfile = splitstandin(standin)
386 lfile = splitstandin(standin)
385 try:
387 try:
386 hash = readstandin(repo, lfile)
388 hash = readstandin(repo, lfile)
387 except IOError:
389 except IOError:
388 hash = None
390 hash = None
389 standins.append((lfile, hash))
391 standins.append((lfile, hash))
390 return standins
392 return standins
391
393
392 def synclfdirstate(repo, lfdirstate, lfile, normallookup):
394 def synclfdirstate(repo, lfdirstate, lfile, normallookup):
393 lfstandin = standin(lfile)
395 lfstandin = standin(lfile)
394 if lfstandin in repo.dirstate:
396 if lfstandin in repo.dirstate:
395 stat = repo.dirstate._map[lfstandin]
397 stat = repo.dirstate._map[lfstandin]
396 state, mtime = stat[0], stat[3]
398 state, mtime = stat[0], stat[3]
397 else:
399 else:
398 state, mtime = '?', -1
400 state, mtime = '?', -1
399 if state == 'n':
401 if state == 'n':
400 if normallookup or mtime < 0:
402 if normallookup or mtime < 0:
401 # state 'n' doesn't ensure 'clean' in this case
403 # state 'n' doesn't ensure 'clean' in this case
402 lfdirstate.normallookup(lfile)
404 lfdirstate.normallookup(lfile)
403 else:
405 else:
404 lfdirstate.normal(lfile)
406 lfdirstate.normal(lfile)
405 elif state == 'm':
407 elif state == 'm':
406 lfdirstate.normallookup(lfile)
408 lfdirstate.normallookup(lfile)
407 elif state == 'r':
409 elif state == 'r':
408 lfdirstate.remove(lfile)
410 lfdirstate.remove(lfile)
409 elif state == 'a':
411 elif state == 'a':
410 lfdirstate.add(lfile)
412 lfdirstate.add(lfile)
411 elif state == '?':
413 elif state == '?':
412 lfdirstate.drop(lfile)
414 lfdirstate.drop(lfile)
413
415
414 def markcommitted(orig, ctx, node):
416 def markcommitted(orig, ctx, node):
415 repo = ctx.repo()
417 repo = ctx.repo()
416
418
417 orig(node)
419 orig(node)
418
420
419 # ATTENTION: "ctx.files()" may differ from "repo[node].files()"
421 # ATTENTION: "ctx.files()" may differ from "repo[node].files()"
420 # because files coming from the 2nd parent are omitted in the latter.
422 # because files coming from the 2nd parent are omitted in the latter.
421 #
423 #
422 # The former should be used to get targets of "synclfdirstate",
424 # The former should be used to get targets of "synclfdirstate",
423 # because such files:
425 # because such files:
424 # - are marked as "a" by "patch.patch()" (e.g. via transplant), and
426 # - are marked as "a" by "patch.patch()" (e.g. via transplant), and
425 # - have to be marked as "n" after commit, but
427 # - have to be marked as "n" after commit, but
426 # - aren't listed in "repo[node].files()"
428 # - aren't listed in "repo[node].files()"
427
429
428 lfdirstate = openlfdirstate(repo.ui, repo)
430 lfdirstate = openlfdirstate(repo.ui, repo)
429 for f in ctx.files():
431 for f in ctx.files():
430 if isstandin(f):
432 if isstandin(f):
431 lfile = splitstandin(f)
433 lfile = splitstandin(f)
432 synclfdirstate(repo, lfdirstate, lfile, False)
434 synclfdirstate(repo, lfdirstate, lfile, False)
433 lfdirstate.write()
435 lfdirstate.write()
434
436
435 # As part of committing, copy all of the largefiles into the cache.
437 # As part of committing, copy all of the largefiles into the cache.
436 copyalltostore(repo, node)
438 copyalltostore(repo, node)
437
439
438 def getlfilestoupdate(oldstandins, newstandins):
440 def getlfilestoupdate(oldstandins, newstandins):
439 changedstandins = set(oldstandins).symmetric_difference(set(newstandins))
441 changedstandins = set(oldstandins).symmetric_difference(set(newstandins))
440 filelist = []
442 filelist = []
441 for f in changedstandins:
443 for f in changedstandins:
442 if f[0] not in filelist:
444 if f[0] not in filelist:
443 filelist.append(f[0])
445 filelist.append(f[0])
444 return filelist
446 return filelist
445
447
446 def getlfilestoupload(repo, missing, addfunc):
448 def getlfilestoupload(repo, missing, addfunc):
447 for i, n in enumerate(missing):
449 for i, n in enumerate(missing):
448 repo.ui.progress(_('finding outgoing largefiles'), i,
450 repo.ui.progress(_('finding outgoing largefiles'), i,
449 unit=_('revision'), total=len(missing))
451 unit=_('revision'), total=len(missing))
450 parents = [p for p in repo.changelog.parents(n) if p != node.nullid]
452 parents = [p for p in repo.changelog.parents(n) if p != node.nullid]
451
453
452 oldlfstatus = repo.lfstatus
454 oldlfstatus = repo.lfstatus
453 repo.lfstatus = False
455 repo.lfstatus = False
454 try:
456 try:
455 ctx = repo[n]
457 ctx = repo[n]
456 finally:
458 finally:
457 repo.lfstatus = oldlfstatus
459 repo.lfstatus = oldlfstatus
458
460
459 files = set(ctx.files())
461 files = set(ctx.files())
460 if len(parents) == 2:
462 if len(parents) == 2:
461 mc = ctx.manifest()
463 mc = ctx.manifest()
462 mp1 = ctx.parents()[0].manifest()
464 mp1 = ctx.parents()[0].manifest()
463 mp2 = ctx.parents()[1].manifest()
465 mp2 = ctx.parents()[1].manifest()
464 for f in mp1:
466 for f in mp1:
465 if f not in mc:
467 if f not in mc:
466 files.add(f)
468 files.add(f)
467 for f in mp2:
469 for f in mp2:
468 if f not in mc:
470 if f not in mc:
469 files.add(f)
471 files.add(f)
470 for f in mc:
472 for f in mc:
471 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
473 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
472 files.add(f)
474 files.add(f)
473 for fn in files:
475 for fn in files:
474 if isstandin(fn) and fn in ctx:
476 if isstandin(fn) and fn in ctx:
475 addfunc(fn, ctx[fn].data().strip())
477 addfunc(fn, ctx[fn].data().strip())
476 repo.ui.progress(_('finding outgoing largefiles'), None)
478 repo.ui.progress(_('finding outgoing largefiles'), None)
477
479
478 def updatestandinsbymatch(repo, match):
480 def updatestandinsbymatch(repo, match):
479 '''Update standins in the working directory according to specified match
481 '''Update standins in the working directory according to specified match
480
482
481 This returns (possibly modified) ``match`` object to be used for
483 This returns (possibly modified) ``match`` object to be used for
482 subsequent commit process.
484 subsequent commit process.
483 '''
485 '''
484
486
485 ui = repo.ui
487 ui = repo.ui
486
488
487 # Case 1: user calls commit with no specific files or
489 # Case 1: user calls commit with no specific files or
488 # include/exclude patterns: refresh and commit all files that
490 # include/exclude patterns: refresh and commit all files that
489 # are "dirty".
491 # are "dirty".
490 if match is None or match.always():
492 if match is None or match.always():
491 # Spend a bit of time here to get a list of files we know
493 # Spend a bit of time here to get a list of files we know
492 # are modified so we can compare only against those.
494 # are modified so we can compare only against those.
493 # It can cost a lot of time (several seconds)
495 # It can cost a lot of time (several seconds)
494 # otherwise to update all standins if the largefiles are
496 # otherwise to update all standins if the largefiles are
495 # large.
497 # large.
496 lfdirstate = openlfdirstate(ui, repo)
498 lfdirstate = openlfdirstate(ui, repo)
497 dirtymatch = match_.always(repo.root, repo.getcwd())
499 dirtymatch = match_.always(repo.root, repo.getcwd())
498 unsure, s = lfdirstate.status(dirtymatch, [], False, False,
500 unsure, s = lfdirstate.status(dirtymatch, [], False, False,
499 False)
501 False)
500 modifiedfiles = unsure + s.modified + s.added + s.removed
502 modifiedfiles = unsure + s.modified + s.added + s.removed
501 lfiles = listlfiles(repo)
503 lfiles = listlfiles(repo)
502 # this only loops through largefiles that exist (not
504 # this only loops through largefiles that exist (not
503 # removed/renamed)
505 # removed/renamed)
504 for lfile in lfiles:
506 for lfile in lfiles:
505 if lfile in modifiedfiles:
507 if lfile in modifiedfiles:
506 if os.path.exists(
508 if os.path.exists(
507 repo.wjoin(standin(lfile))):
509 repo.wjoin(standin(lfile))):
508 # this handles the case where a rebase is being
510 # this handles the case where a rebase is being
509 # performed and the working copy is not updated
511 # performed and the working copy is not updated
510 # yet.
512 # yet.
511 if os.path.exists(repo.wjoin(lfile)):
513 if os.path.exists(repo.wjoin(lfile)):
512 updatestandin(repo,
514 updatestandin(repo,
513 standin(lfile))
515 standin(lfile))
514
516
515 return match
517 return match
516
518
517 lfiles = listlfiles(repo)
519 lfiles = listlfiles(repo)
518 match._files = repo._subdirlfs(match.files(), lfiles)
520 match._files = repo._subdirlfs(match.files(), lfiles)
519
521
520 # Case 2: user calls commit with specified patterns: refresh
522 # Case 2: user calls commit with specified patterns: refresh
521 # any matching big files.
523 # any matching big files.
522 smatcher = composestandinmatcher(repo, match)
524 smatcher = composestandinmatcher(repo, match)
523 standins = repo.dirstate.walk(smatcher, [], False, False)
525 standins = repo.dirstate.walk(smatcher, [], False, False)
524
526
525 # No matching big files: get out of the way and pass control to
527 # No matching big files: get out of the way and pass control to
526 # the usual commit() method.
528 # the usual commit() method.
527 if not standins:
529 if not standins:
528 return match
530 return match
529
531
530 # Refresh all matching big files. It's possible that the
532 # Refresh all matching big files. It's possible that the
531 # commit will end up failing, in which case the big files will
533 # commit will end up failing, in which case the big files will
532 # stay refreshed. No harm done: the user modified them and
534 # stay refreshed. No harm done: the user modified them and
533 # asked to commit them, so sooner or later we're going to
535 # asked to commit them, so sooner or later we're going to
534 # refresh the standins. Might as well leave them refreshed.
536 # refresh the standins. Might as well leave them refreshed.
535 lfdirstate = openlfdirstate(ui, repo)
537 lfdirstate = openlfdirstate(ui, repo)
536 for fstandin in standins:
538 for fstandin in standins:
537 lfile = splitstandin(fstandin)
539 lfile = splitstandin(fstandin)
538 if lfdirstate[lfile] != 'r':
540 if lfdirstate[lfile] != 'r':
539 updatestandin(repo, fstandin)
541 updatestandin(repo, fstandin)
540
542
541 # Cook up a new matcher that only matches regular files or
543 # Cook up a new matcher that only matches regular files or
542 # standins corresponding to the big files requested by the
544 # standins corresponding to the big files requested by the
543 # user. Have to modify _files to prevent commit() from
545 # user. Have to modify _files to prevent commit() from
544 # complaining "not tracked" for big files.
546 # complaining "not tracked" for big files.
545 match = copy.copy(match)
547 match = copy.copy(match)
546 origmatchfn = match.matchfn
548 origmatchfn = match.matchfn
547
549
548 # Check both the list of largefiles and the list of
550 # Check both the list of largefiles and the list of
549 # standins because if a largefile was removed, it
551 # standins because if a largefile was removed, it
550 # won't be in the list of largefiles at this point
552 # won't be in the list of largefiles at this point
551 match._files += sorted(standins)
553 match._files += sorted(standins)
552
554
553 actualfiles = []
555 actualfiles = []
554 for f in match._files:
556 for f in match._files:
555 fstandin = standin(f)
557 fstandin = standin(f)
556
558
557 # ignore known largefiles and standins
559 # ignore known largefiles and standins
558 if f in lfiles or fstandin in standins:
560 if f in lfiles or fstandin in standins:
559 continue
561 continue
560
562
561 actualfiles.append(f)
563 actualfiles.append(f)
562 match._files = actualfiles
564 match._files = actualfiles
563
565
564 def matchfn(f):
566 def matchfn(f):
565 if origmatchfn(f):
567 if origmatchfn(f):
566 return f not in lfiles
568 return f not in lfiles
567 else:
569 else:
568 return f in standins
570 return f in standins
569
571
570 match.matchfn = matchfn
572 match.matchfn = matchfn
571
573
572 return match
574 return match
573
575
574 class automatedcommithook(object):
576 class automatedcommithook(object):
575 '''Stateful hook to update standins at the 1st commit of resuming
577 '''Stateful hook to update standins at the 1st commit of resuming
576
578
577 For efficiency, updating standins in the working directory should
579 For efficiency, updating standins in the working directory should
578 be avoided while automated committing (like rebase, transplant and
580 be avoided while automated committing (like rebase, transplant and
579 so on), because they should be updated before committing.
581 so on), because they should be updated before committing.
580
582
581 But the 1st commit of resuming automated committing (e.g. ``rebase
583 But the 1st commit of resuming automated committing (e.g. ``rebase
582 --continue``) should update them, because largefiles may be
584 --continue``) should update them, because largefiles may be
583 modified manually.
585 modified manually.
584 '''
586 '''
585 def __init__(self, resuming):
587 def __init__(self, resuming):
586 self.resuming = resuming
588 self.resuming = resuming
587
589
588 def __call__(self, repo, match):
590 def __call__(self, repo, match):
589 if self.resuming:
591 if self.resuming:
590 self.resuming = False # avoids updating at subsequent commits
592 self.resuming = False # avoids updating at subsequent commits
591 return updatestandinsbymatch(repo, match)
593 return updatestandinsbymatch(repo, match)
592 else:
594 else:
593 return match
595 return match
594
596
595 def getstatuswriter(ui, repo, forcibly=None):
597 def getstatuswriter(ui, repo, forcibly=None):
596 '''Return the function to write largefiles specific status out
598 '''Return the function to write largefiles specific status out
597
599
598 If ``forcibly`` is ``None``, this returns the last element of
600 If ``forcibly`` is ``None``, this returns the last element of
599 ``repo._lfstatuswriters`` as "default" writer function.
601 ``repo._lfstatuswriters`` as "default" writer function.
600
602
601 Otherwise, this returns the function to always write out (or
603 Otherwise, this returns the function to always write out (or
602 ignore if ``not forcibly``) status.
604 ignore if ``not forcibly``) status.
603 '''
605 '''
604 if forcibly is None and util.safehasattr(repo, '_largefilesenabled'):
606 if forcibly is None and util.safehasattr(repo, '_largefilesenabled'):
605 return repo._lfstatuswriters[-1]
607 return repo._lfstatuswriters[-1]
606 else:
608 else:
607 if forcibly:
609 if forcibly:
608 return ui.status # forcibly WRITE OUT
610 return ui.status # forcibly WRITE OUT
609 else:
611 else:
610 return lambda *msg, **opts: None # forcibly IGNORE
612 return lambda *msg, **opts: None # forcibly IGNORE
@@ -1,508 +1,512
1 Test histedit extension: Fold commands
1 Test histedit extension: Fold commands
2 ======================================
2 ======================================
3
3
4 This test file is dedicated to testing the fold command in non conflicting
4 This test file is dedicated to testing the fold command in non conflicting
5 case.
5 case.
6
6
7 Initialization
7 Initialization
8 ---------------
8 ---------------
9
9
10
10
11 $ . "$TESTDIR/histedit-helpers.sh"
11 $ . "$TESTDIR/histedit-helpers.sh"
12
12
13 $ cat >> $HGRCPATH <<EOF
13 $ cat >> $HGRCPATH <<EOF
14 > [alias]
14 > [alias]
15 > logt = log --template '{rev}:{node|short} {desc|firstline}\n'
15 > logt = log --template '{rev}:{node|short} {desc|firstline}\n'
16 > [extensions]
16 > [extensions]
17 > histedit=
17 > histedit=
18 > EOF
18 > EOF
19
19
20
20
21 Simple folding
21 Simple folding
22 --------------------
22 --------------------
23 $ initrepo ()
23 $ initrepo ()
24 > {
24 > {
25 > hg init r
25 > hg init r
26 > cd r
26 > cd r
27 > for x in a b c d e f ; do
27 > for x in a b c d e f ; do
28 > echo $x > $x
28 > echo $x > $x
29 > hg add $x
29 > hg add $x
30 > hg ci -m $x
30 > hg ci -m $x
31 > done
31 > done
32 > }
32 > }
33
33
34 $ initrepo
34 $ initrepo
35
35
36 log before edit
36 log before edit
37 $ hg logt --graph
37 $ hg logt --graph
38 @ 5:652413bf663e f
38 @ 5:652413bf663e f
39 |
39 |
40 o 4:e860deea161a e
40 o 4:e860deea161a e
41 |
41 |
42 o 3:055a42cdd887 d
42 o 3:055a42cdd887 d
43 |
43 |
44 o 2:177f92b77385 c
44 o 2:177f92b77385 c
45 |
45 |
46 o 1:d2ae7f538514 b
46 o 1:d2ae7f538514 b
47 |
47 |
48 o 0:cb9a9f314b8b a
48 o 0:cb9a9f314b8b a
49
49
50
50
51 $ hg histedit 177f92b77385 --commands - 2>&1 <<EOF | fixbundle
51 $ hg histedit 177f92b77385 --commands - 2>&1 <<EOF | fixbundle
52 > pick e860deea161a e
52 > pick e860deea161a e
53 > pick 652413bf663e f
53 > pick 652413bf663e f
54 > fold 177f92b77385 c
54 > fold 177f92b77385 c
55 > pick 055a42cdd887 d
55 > pick 055a42cdd887 d
56 > EOF
56 > EOF
57 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
57 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
58 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
58 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
59 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
59 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
60 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
60 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
61 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
61 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
62 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
62 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
63 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
63 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
64
64
65 log after edit
65 log after edit
66 $ hg logt --graph
66 $ hg logt --graph
67 @ 4:9c277da72c9b d
67 @ 4:9c277da72c9b d
68 |
68 |
69 o 3:6de59d13424a f
69 o 3:6de59d13424a f
70 |
70 |
71 o 2:ee283cb5f2d5 e
71 o 2:ee283cb5f2d5 e
72 |
72 |
73 o 1:d2ae7f538514 b
73 o 1:d2ae7f538514 b
74 |
74 |
75 o 0:cb9a9f314b8b a
75 o 0:cb9a9f314b8b a
76
76
77
77
78 post-fold manifest
78 post-fold manifest
79 $ hg manifest
79 $ hg manifest
80 a
80 a
81 b
81 b
82 c
82 c
83 d
83 d
84 e
84 e
85 f
85 f
86
86
87
87
88 check histedit_source
88 check histedit_source
89
89
90 $ hg log --debug --rev 3
90 $ hg log --debug --rev 3
91 changeset: 3:6de59d13424a8a13acd3e975514aed29dd0d9b2d
91 changeset: 3:6de59d13424a8a13acd3e975514aed29dd0d9b2d
92 phase: draft
92 phase: draft
93 parent: 2:ee283cb5f2d5955443f23a27b697a04339e9a39a
93 parent: 2:ee283cb5f2d5955443f23a27b697a04339e9a39a
94 parent: -1:0000000000000000000000000000000000000000
94 parent: -1:0000000000000000000000000000000000000000
95 manifest: 3:81eede616954057198ead0b2c73b41d1f392829a
95 manifest: 3:81eede616954057198ead0b2c73b41d1f392829a
96 user: test
96 user: test
97 date: Thu Jan 01 00:00:00 1970 +0000
97 date: Thu Jan 01 00:00:00 1970 +0000
98 files+: c f
98 files+: c f
99 extra: branch=default
99 extra: branch=default
100 extra: histedit_source=a4f7421b80f79fcc59fff01bcbf4a53d127dd6d3,177f92b773850b59254aa5e923436f921b55483b
100 extra: histedit_source=a4f7421b80f79fcc59fff01bcbf4a53d127dd6d3,177f92b773850b59254aa5e923436f921b55483b
101 description:
101 description:
102 f
102 f
103 ***
103 ***
104 c
104 c
105
105
106
106
107
107
108 rollup will fold without preserving the folded commit's message
108 rollup will fold without preserving the folded commit's message
109
109
110 $ OLDHGEDITOR=$HGEDITOR
110 $ OLDHGEDITOR=$HGEDITOR
111 $ HGEDITOR=false
111 $ HGEDITOR=false
112 $ hg histedit d2ae7f538514 --commands - 2>&1 <<EOF | fixbundle
112 $ hg histedit d2ae7f538514 --commands - 2>&1 <<EOF | fixbundle
113 > pick d2ae7f538514 b
113 > pick d2ae7f538514 b
114 > roll ee283cb5f2d5 e
114 > roll ee283cb5f2d5 e
115 > pick 6de59d13424a f
115 > pick 6de59d13424a f
116 > pick 9c277da72c9b d
116 > pick 9c277da72c9b d
117 > EOF
117 > EOF
118 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
118 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
119 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
119 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
120 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
120 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
121 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
121 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
122 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
122 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
123 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
123 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
124
124
125 $ HGEDITOR=$OLDHGEDITOR
125 $ HGEDITOR=$OLDHGEDITOR
126
126
127 log after edit
127 log after edit
128 $ hg logt --graph
128 $ hg logt --graph
129 @ 3:c4a9eb7989fc d
129 @ 3:c4a9eb7989fc d
130 |
130 |
131 o 2:8e03a72b6f83 f
131 o 2:8e03a72b6f83 f
132 |
132 |
133 o 1:391ee782c689 b
133 o 1:391ee782c689 b
134 |
134 |
135 o 0:cb9a9f314b8b a
135 o 0:cb9a9f314b8b a
136
136
137
137
138 description is taken from rollup target commit
138 description is taken from rollup target commit
139
139
140 $ hg log --debug --rev 1
140 $ hg log --debug --rev 1
141 changeset: 1:391ee782c68930be438ccf4c6a403daedbfbffa5
141 changeset: 1:391ee782c68930be438ccf4c6a403daedbfbffa5
142 phase: draft
142 phase: draft
143 parent: 0:cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
143 parent: 0:cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
144 parent: -1:0000000000000000000000000000000000000000
144 parent: -1:0000000000000000000000000000000000000000
145 manifest: 1:b5e112a3a8354e269b1524729f0918662d847c38
145 manifest: 1:b5e112a3a8354e269b1524729f0918662d847c38
146 user: test
146 user: test
147 date: Thu Jan 01 00:00:00 1970 +0000
147 date: Thu Jan 01 00:00:00 1970 +0000
148 files+: b e
148 files+: b e
149 extra: branch=default
149 extra: branch=default
150 extra: histedit_source=d2ae7f538514cd87c17547b0de4cea71fe1af9fb,ee283cb5f2d5955443f23a27b697a04339e9a39a
150 extra: histedit_source=d2ae7f538514cd87c17547b0de4cea71fe1af9fb,ee283cb5f2d5955443f23a27b697a04339e9a39a
151 description:
151 description:
152 b
152 b
153
153
154
154
155
155
156 check saving last-message.txt
156 check saving last-message.txt
157
157
158 $ cat > $TESTTMP/abortfolding.py <<EOF
158 $ cat > $TESTTMP/abortfolding.py <<EOF
159 > from mercurial import util
159 > from mercurial import util
160 > def abortfolding(ui, repo, hooktype, **kwargs):
160 > def abortfolding(ui, repo, hooktype, **kwargs):
161 > ctx = repo[kwargs.get('node')]
161 > ctx = repo[kwargs.get('node')]
162 > if set(ctx.files()) == set(['c', 'd', 'f']):
162 > if set(ctx.files()) == set(['c', 'd', 'f']):
163 > return True # abort folding commit only
163 > return True # abort folding commit only
164 > ui.warn('allow non-folding commit\\n')
164 > ui.warn('allow non-folding commit\\n')
165 > EOF
165 > EOF
166 $ cat > .hg/hgrc <<EOF
166 $ cat > .hg/hgrc <<EOF
167 > [hooks]
167 > [hooks]
168 > pretxncommit.abortfolding = python:$TESTTMP/abortfolding.py:abortfolding
168 > pretxncommit.abortfolding = python:$TESTTMP/abortfolding.py:abortfolding
169 > EOF
169 > EOF
170
170
171 $ cat > $TESTTMP/editor.sh << EOF
171 $ cat > $TESTTMP/editor.sh << EOF
172 > echo "==== before editing"
172 > echo "==== before editing"
173 > cat \$1
173 > cat \$1
174 > echo "===="
174 > echo "===="
175 > echo "check saving last-message.txt" >> \$1
175 > echo "check saving last-message.txt" >> \$1
176 > EOF
176 > EOF
177
177
178 $ rm -f .hg/last-message.txt
178 $ rm -f .hg/last-message.txt
179 $ hg status --rev '8e03a72b6f83^1::c4a9eb7989fc'
179 $ hg status --rev '8e03a72b6f83^1::c4a9eb7989fc'
180 A c
180 A c
181 A d
181 A d
182 A f
182 A f
183 $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit 8e03a72b6f83 --commands - 2>&1 <<EOF
183 $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit 8e03a72b6f83 --commands - 2>&1 <<EOF
184 > pick 8e03a72b6f83 f
184 > pick 8e03a72b6f83 f
185 > fold c4a9eb7989fc d
185 > fold c4a9eb7989fc d
186 > EOF
186 > EOF
187 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
187 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
188 adding d
188 adding d
189 allow non-folding commit
189 allow non-folding commit
190 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
190 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
191 ==== before editing
191 ==== before editing
192 f
192 f
193 ***
193 ***
194 c
194 c
195 ***
195 ***
196 d
196 d
197
197
198
198
199
199
200 HG: Enter commit message. Lines beginning with 'HG:' are removed.
200 HG: Enter commit message. Lines beginning with 'HG:' are removed.
201 HG: Leave message empty to abort commit.
201 HG: Leave message empty to abort commit.
202 HG: --
202 HG: --
203 HG: user: test
203 HG: user: test
204 HG: branch 'default'
204 HG: branch 'default'
205 HG: added c
205 HG: added c
206 HG: added d
206 HG: added d
207 HG: added f
207 HG: added f
208 ====
208 ====
209 transaction abort!
209 transaction abort!
210 rollback completed
210 rollback completed
211 abort: pretxncommit.abortfolding hook failed
211 abort: pretxncommit.abortfolding hook failed
212 [255]
212 [255]
213
213
214 $ cat .hg/last-message.txt
214 $ cat .hg/last-message.txt
215 f
215 f
216 ***
216 ***
217 c
217 c
218 ***
218 ***
219 d
219 d
220
220
221
221
222
222
223 check saving last-message.txt
223 check saving last-message.txt
224
224
225 $ cd ..
225 $ cd ..
226 $ rm -r r
226 $ rm -r r
227
227
228 folding preserves initial author
228 folding preserves initial author
229 --------------------------------
229 --------------------------------
230
230
231 $ initrepo
231 $ initrepo
232
232
233 $ hg ci --user "someone else" --amend --quiet
233 $ hg ci --user "someone else" --amend --quiet
234
234
235 tip before edit
235 tip before edit
236 $ hg log --rev .
236 $ hg log --rev .
237 changeset: 5:a00ad806cb55
237 changeset: 5:a00ad806cb55
238 tag: tip
238 tag: tip
239 user: someone else
239 user: someone else
240 date: Thu Jan 01 00:00:00 1970 +0000
240 date: Thu Jan 01 00:00:00 1970 +0000
241 summary: f
241 summary: f
242
242
243
243
244 $ hg histedit e860deea161a --commands - 2>&1 <<EOF | fixbundle
244 $ hg histedit e860deea161a --commands - 2>&1 <<EOF | fixbundle
245 > pick e860deea161a e
245 > pick e860deea161a e
246 > fold a00ad806cb55 f
246 > fold a00ad806cb55 f
247 > EOF
247 > EOF
248 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
248 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
249 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
249 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
250 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
250 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
251 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
251 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
252
252
253 tip after edit
253 tip after edit
254 $ hg log --rev .
254 $ hg log --rev .
255 changeset: 4:698d4e8040a1
255 changeset: 4:698d4e8040a1
256 tag: tip
256 tag: tip
257 user: test
257 user: test
258 date: Thu Jan 01 00:00:00 1970 +0000
258 date: Thu Jan 01 00:00:00 1970 +0000
259 summary: e
259 summary: e
260
260
261
261
262 $ cd ..
262 $ cd ..
263 $ rm -r r
263 $ rm -r r
264
264
265 folding and creating no new change doesn't break:
265 folding and creating no new change doesn't break:
266 -------------------------------------------------
266 -------------------------------------------------
267
267
268 folded content is dropped during a merge. The folded commit should properly disappear.
268 folded content is dropped during a merge. The folded commit should properly disappear.
269
269
270 $ mkdir fold-to-empty-test
270 $ mkdir fold-to-empty-test
271 $ cd fold-to-empty-test
271 $ cd fold-to-empty-test
272 $ hg init
272 $ hg init
273 $ printf "1\n2\n3\n" > file
273 $ printf "1\n2\n3\n" > file
274 $ hg add file
274 $ hg add file
275 $ hg commit -m '1+2+3'
275 $ hg commit -m '1+2+3'
276 $ echo 4 >> file
276 $ echo 4 >> file
277 $ hg commit -m '+4'
277 $ hg commit -m '+4'
278 $ echo 5 >> file
278 $ echo 5 >> file
279 $ hg commit -m '+5'
279 $ hg commit -m '+5'
280 $ echo 6 >> file
280 $ echo 6 >> file
281 $ hg commit -m '+6'
281 $ hg commit -m '+6'
282 $ hg logt --graph
282 $ hg logt --graph
283 @ 3:251d831eeec5 +6
283 @ 3:251d831eeec5 +6
284 |
284 |
285 o 2:888f9082bf99 +5
285 o 2:888f9082bf99 +5
286 |
286 |
287 o 1:617f94f13c0f +4
287 o 1:617f94f13c0f +4
288 |
288 |
289 o 0:0189ba417d34 1+2+3
289 o 0:0189ba417d34 1+2+3
290
290
291
291
292 $ hg histedit 1 --commands - << EOF
292 $ hg histedit 1 --commands - << EOF
293 > pick 617f94f13c0f 1 +4
293 > pick 617f94f13c0f 1 +4
294 > drop 888f9082bf99 2 +5
294 > drop 888f9082bf99 2 +5
295 > fold 251d831eeec5 3 +6
295 > fold 251d831eeec5 3 +6
296 > EOF
296 > EOF
297 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
297 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
298 merging file
298 merging file
299 warning: conflicts during merge.
299 warning: conflicts during merge.
300 merging file incomplete! (edit conflicts, then use 'hg resolve --mark')
300 merging file incomplete! (edit conflicts, then use 'hg resolve --mark')
301 Fix up the change and run hg histedit --continue
301 Fix up the change and run hg histedit --continue
302 [1]
302 [1]
303 There were conflicts, we keep P1 content. This
303 There were conflicts, we keep P1 content. This
304 should effectively drop the changes from +6.
304 should effectively drop the changes from +6.
305 $ hg status
305 $ hg status
306 M file
306 M file
307 ? file.orig
307 ? file.orig
308 $ hg resolve -l
308 $ hg resolve -l
309 U file
309 U file
310 $ hg revert -r 'p1()' file
310 $ hg revert -r 'p1()' file
311 $ hg resolve --mark file
311 $ hg resolve --mark file
312 (no more unresolved files)
312 (no more unresolved files)
313 $ hg histedit --continue
313 $ hg histedit --continue
314 251d831eeec5: empty changeset
314 251d831eeec5: empty changeset
315 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
315 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
316 saved backup bundle to $TESTTMP/*-backup.hg (glob)
316 saved backup bundle to $TESTTMP/*-backup.hg (glob)
317 $ hg logt --graph
317 $ hg logt --graph
318 @ 1:617f94f13c0f +4
318 @ 1:617f94f13c0f +4
319 |
319 |
320 o 0:0189ba417d34 1+2+3
320 o 0:0189ba417d34 1+2+3
321
321
322
322
323 $ cd ..
323 $ cd ..
324
324
325
325
326 Test fold through dropped
326 Test fold through dropped
327 -------------------------
327 -------------------------
328
328
329
329
330 Test corner case where folded revision is separated from its parent by a
330 Test corner case where folded revision is separated from its parent by a
331 dropped revision.
331 dropped revision.
332
332
333
333
334 $ hg init fold-with-dropped
334 $ hg init fold-with-dropped
335 $ cd fold-with-dropped
335 $ cd fold-with-dropped
336 $ printf "1\n2\n3\n" > file
336 $ printf "1\n2\n3\n" > file
337 $ hg commit -Am '1+2+3'
337 $ hg commit -Am '1+2+3'
338 adding file
338 adding file
339 $ echo 4 >> file
339 $ echo 4 >> file
340 $ hg commit -m '+4'
340 $ hg commit -m '+4'
341 $ echo 5 >> file
341 $ echo 5 >> file
342 $ hg commit -m '+5'
342 $ hg commit -m '+5'
343 $ echo 6 >> file
343 $ echo 6 >> file
344 $ hg commit -m '+6'
344 $ hg commit -m '+6'
345 $ hg logt -G
345 $ hg logt -G
346 @ 3:251d831eeec5 +6
346 @ 3:251d831eeec5 +6
347 |
347 |
348 o 2:888f9082bf99 +5
348 o 2:888f9082bf99 +5
349 |
349 |
350 o 1:617f94f13c0f +4
350 o 1:617f94f13c0f +4
351 |
351 |
352 o 0:0189ba417d34 1+2+3
352 o 0:0189ba417d34 1+2+3
353
353
354 $ hg histedit 1 --commands - << EOF
354 $ hg histedit 1 --commands - << EOF
355 > pick 617f94f13c0f 1 +4
355 > pick 617f94f13c0f 1 +4
356 > drop 888f9082bf99 2 +5
356 > drop 888f9082bf99 2 +5
357 > fold 251d831eeec5 3 +6
357 > fold 251d831eeec5 3 +6
358 > EOF
358 > EOF
359 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
359 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
360 merging file
360 merging file
361 warning: conflicts during merge.
361 warning: conflicts during merge.
362 merging file incomplete! (edit conflicts, then use 'hg resolve --mark')
362 merging file incomplete! (edit conflicts, then use 'hg resolve --mark')
363 Fix up the change and run hg histedit --continue
363 Fix up the change and run hg histedit --continue
364 [1]
364 [1]
365 $ cat > file << EOF
365 $ cat > file << EOF
366 > 1
366 > 1
367 > 2
367 > 2
368 > 3
368 > 3
369 > 4
369 > 4
370 > 5
370 > 5
371 > EOF
371 > EOF
372 $ hg resolve --mark file
372 $ hg resolve --mark file
373 (no more unresolved files)
373 (no more unresolved files)
374 $ hg commit -m '+5.2'
374 $ hg commit -m '+5.2'
375 created new head
375 created new head
376 $ echo 6 >> file
376 $ echo 6 >> file
377 $ HGEDITOR=cat hg histedit --continue
377 $ HGEDITOR=cat hg histedit --continue
378 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
378 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
379 +4
379 +4
380 ***
380 ***
381 +5.2
381 +5.2
382 ***
382 ***
383 +6
383 +6
384
384
385
385
386
386
387 HG: Enter commit message. Lines beginning with 'HG:' are removed.
387 HG: Enter commit message. Lines beginning with 'HG:' are removed.
388 HG: Leave message empty to abort commit.
388 HG: Leave message empty to abort commit.
389 HG: --
389 HG: --
390 HG: user: test
390 HG: user: test
391 HG: branch 'default'
391 HG: branch 'default'
392 HG: changed file
392 HG: changed file
393 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
393 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
394 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
394 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
395 saved backup bundle to $TESTTMP/fold-with-dropped/.hg/strip-backup/617f94f13c0f-3d69522c-backup.hg (glob)
395 saved backup bundle to $TESTTMP/fold-with-dropped/.hg/strip-backup/617f94f13c0f-3d69522c-backup.hg (glob)
396 $ hg logt -G
396 $ hg logt -G
397 @ 1:10c647b2cdd5 +4
397 @ 1:10c647b2cdd5 +4
398 |
398 |
399 o 0:0189ba417d34 1+2+3
399 o 0:0189ba417d34 1+2+3
400
400
401 $ hg export tip
401 $ hg export tip
402 # HG changeset patch
402 # HG changeset patch
403 # User test
403 # User test
404 # Date 0 0
404 # Date 0 0
405 # Thu Jan 01 00:00:00 1970 +0000
405 # Thu Jan 01 00:00:00 1970 +0000
406 # Node ID 10c647b2cdd54db0603ecb99b2ff5ce66d5a5323
406 # Node ID 10c647b2cdd54db0603ecb99b2ff5ce66d5a5323
407 # Parent 0189ba417d34df9dda55f88b637dcae9917b5964
407 # Parent 0189ba417d34df9dda55f88b637dcae9917b5964
408 +4
408 +4
409 ***
409 ***
410 +5.2
410 +5.2
411 ***
411 ***
412 +6
412 +6
413
413
414 diff -r 0189ba417d34 -r 10c647b2cdd5 file
414 diff -r 0189ba417d34 -r 10c647b2cdd5 file
415 --- a/file Thu Jan 01 00:00:00 1970 +0000
415 --- a/file Thu Jan 01 00:00:00 1970 +0000
416 +++ b/file Thu Jan 01 00:00:00 1970 +0000
416 +++ b/file Thu Jan 01 00:00:00 1970 +0000
417 @@ -1,3 +1,6 @@
417 @@ -1,3 +1,6 @@
418 1
418 1
419 2
419 2
420 3
420 3
421 +4
421 +4
422 +5
422 +5
423 +6
423 +6
424 $ cd ..
424 $ cd ..
425
425
426
426
427 Folding with initial rename (issue3729)
427 Folding with initial rename (issue3729)
428 ---------------------------------------
428 ---------------------------------------
429
429
430 $ hg init fold-rename
430 $ hg init fold-rename
431 $ cd fold-rename
431 $ cd fold-rename
432 $ echo a > a.txt
432 $ echo a > a.txt
433 $ hg add a.txt
433 $ hg add a.txt
434 $ hg commit -m a
434 $ hg commit -m a
435 $ hg rename a.txt b.txt
435 $ hg rename a.txt b.txt
436 $ hg commit -m rename
436 $ hg commit -m rename
437 $ echo b >> b.txt
437 $ echo b >> b.txt
438 $ hg commit -m b
438 $ hg commit -m b
439
439
440 $ hg logt --follow b.txt
440 $ hg logt --follow b.txt
441 2:e0371e0426bc b
441 2:e0371e0426bc b
442 1:1c4f440a8085 rename
442 1:1c4f440a8085 rename
443 0:6c795aa153cb a
443 0:6c795aa153cb a
444
444
445 $ hg histedit 1c4f440a8085 --commands - 2>&1 << EOF | fixbundle
445 $ hg histedit 1c4f440a8085 --commands - 2>&1 << EOF | fixbundle
446 > pick 1c4f440a8085 rename
446 > pick 1c4f440a8085 rename
447 > fold e0371e0426bc b
447 > fold e0371e0426bc b
448 > EOF
448 > EOF
449 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
449 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
450 reverting b.txt
450 reverting b.txt
451 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
451 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
452 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
452 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
453 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
453 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
454
454
455 $ hg logt --follow b.txt
455 $ hg logt --follow b.txt
456 1:cf858d235c76 rename
456 1:cf858d235c76 rename
457 0:6c795aa153cb a
457 0:6c795aa153cb a
458
458
459 $ cd ..
459 $ cd ..
460
460
461 Folding with swapping
461 Folding with swapping
462 ---------------------
462 ---------------------
463
463
464 This is an excuse to test hook with histedit temporary commit (issue4422)
464 This is an excuse to test hook with histedit temporary commit (issue4422)
465
465
466
466
467 $ hg init issue4422
467 $ hg init issue4422
468 $ cd issue4422
468 $ cd issue4422
469 $ echo a > a.txt
469 $ echo a > a.txt
470 $ hg add a.txt
470 $ hg add a.txt
471 $ hg commit -m a
471 $ hg commit -m a
472 $ echo b > b.txt
472 $ echo b > b.txt
473 $ hg add b.txt
473 $ hg add b.txt
474 $ hg commit -m b
474 $ hg commit -m b
475 $ echo c > c.txt
475 $ echo c > c.txt
476 $ hg add c.txt
476 $ hg add c.txt
477 $ hg commit -m c
477 $ hg commit -m c
478
478
479 $ hg logt
479 $ hg logt
480 2:a1a953ffb4b0 c
480 2:a1a953ffb4b0 c
481 1:199b6bb90248 b
481 1:199b6bb90248 b
482 0:6c795aa153cb a
482 0:6c795aa153cb a
483
483
484 Setup the proper environment variable symbol for the platform, to be subbed
484 Setup the proper environment variable symbol for the platform, to be subbed
485 into the hook command.
485 into the hook command.
486 #if windows
486 #if windows
487 $ NODE="%HG_NODE%"
487 $ NODE="%HG_NODE%"
488 #else
488 #else
489 $ NODE="\$HG_NODE"
489 $ NODE="\$HG_NODE"
490 #endif
490 #endif
491 $ hg histedit 6c795aa153cb --config hooks.commit="echo commit $NODE" --commands - 2>&1 << EOF | fixbundle
491 $ hg histedit 6c795aa153cb --config hooks.commit="echo commit $NODE" --commands - 2>&1 << EOF | fixbundle
492 > pick 199b6bb90248 b
492 > pick 199b6bb90248 b
493 > fold a1a953ffb4b0 c
493 > fold a1a953ffb4b0 c
494 > pick 6c795aa153cb a
494 > pick 6c795aa153cb a
495 > EOF
495 > EOF
496 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
496 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
497 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
497 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
498 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
498 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
499 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
499 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
500 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
500 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
501 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
501 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
502 commit 9599899f62c05f4377548c32bf1c9f1a39634b0c
502 commit 9599899f62c05f4377548c32bf1c9f1a39634b0c
503
503
504 $ hg logt
504 $ hg logt
505 1:9599899f62c0 a
505 1:9599899f62c0 a
506 0:79b99e9c8e49 b
506 0:79b99e9c8e49 b
507
507
508 $ echo "foo" > amended.txt
509 $ hg add amended.txt
510 $ hg ci -q --config extensions.largefiles= --amend -I amended.txt
511
508 $ cd ..
512 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now