##// END OF EJS Templates
bookmarks: resolve divergent bookmarks when moving active bookmark forward...
bookmarks: resolve divergent bookmarks when moving active bookmark forward This patch resolves divergent bookmarks between the current active bookmark MARK and the new destination. This situation can arise when pulling new changesets, abandoning your current changesets actively bookmarked with MARK via strip, and then doing a bare update. The non-divergent but active bookmark MARK is then moved to a common ancestor of the new changesets and the abandoned changesets. Test coverage is added.

File last commit:

r19089:0509ae08 stable
r19110:741d94aa stable
Show More
lfutil.py
367 lines | 12.7 KiB | text/x-python | PythonLexer
various
hgext: add largefiles extension...
r15168 # Copyright 2009-2010 Gregory P. Ward
# Copyright 2009-2010 Intelerad Medical Systems Incorporated
# Copyright 2010-2011 Fog Creek Software
# Copyright 2010-2011 Unity Technologies
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
'''largefiles utility code: must not import other modules in this package.'''
import os
Benjamin Pollack
largefiles: use XDG and OS X-specific cache locations by default (issue3067)
r15320 import platform
various
hgext: add largefiles extension...
r15168 import shutil
import stat
Na'Tosha Bard
largefiles: cleanup import, now that we can assume > 1.9 for bundled extension
r15226 from mercurial import dirstate, httpconnection, match as match_, util, scmutil
various
hgext: add largefiles extension...
r15168 from mercurial.i18n import _
shortname = '.hglf'
Mads Kiilerich
largefiles: use constant for '.hglf/'...
r18151 shortnameslash = shortname + '/'
various
hgext: add largefiles extension...
r15168 longname = 'largefiles'
# -- Private worker functions ------------------------------------------
Greg Ward
largefiles: factor out lfutil.getminsize()
r15227 def getminsize(ui, assumelfiles, opt, default=10):
lfsize = opt
if not lfsize and assumelfiles:
Greg Ward
largefiles: rename config setting 'size' to 'minsize'
r15304 lfsize = ui.config(longname, 'minsize', default=default)
Greg Ward
largefiles: factor out lfutil.getminsize()
r15227 if lfsize:
try:
Greg Ward
largefiles: allow minimum size to be a float...
r15228 lfsize = float(lfsize)
Greg Ward
largefiles: factor out lfutil.getminsize()
r15227 except ValueError:
Greg Ward
largefiles: allow minimum size to be a float...
r15228 raise util.Abort(_('largefiles: size must be number (not %s)\n')
Greg Ward
largefiles: factor out lfutil.getminsize()
r15227 % lfsize)
if lfsize is None:
raise util.Abort(_('minimum size for largefiles must be specified'))
return lfsize
various
hgext: add largefiles extension...
r15168 def link(src, dest):
Mads Kiilerich
largefiles: refactoring - create destination dir in lfutil.link
r18998 util.makedirs(os.path.dirname(dest))
various
hgext: add largefiles extension...
r15168 try:
Na'Tosha Bard
largefiles: fix commit of specified file on non-windows
r15206 util.oslink(src, dest)
various
hgext: add largefiles extension...
r15168 except OSError:
Martin Geisler
largefiles: copy files into .hg/largefiles atomically...
r15572 # if hardlinks fail, fallback on atomic copy
dst = util.atomictempfile(dest)
Matt Mackall
largefiles: copy files in binary mode (issue3164)
r15699 for chunk in util.filechunkiter(open(src, 'rb')):
Martin Geisler
largefiles: copy files into .hg/largefiles atomically...
r15572 dst.write(chunk)
dst.close()
various
hgext: add largefiles extension...
r15168 os.chmod(dest, os.stat(src).st_mode)
Benjamin Pollack
largefiles: rename functions and methods to match desired behavior...
r15316 def usercachepath(ui, hash):
Greg Ward
largefiles: use ui.configpath() where appropriate
r15350 path = ui.configpath(longname, 'usercache', None)
various
hgext: add largefiles extension...
r15168 if path:
path = os.path.join(path, hash)
else:
if os.name == 'nt':
Greg Ward
largefiles: cosmetics, whitespace, code style...
r15255 appdata = os.getenv('LOCALAPPDATA', os.getenv('APPDATA'))
Kevin Gessner
largefiles: don't require a user cache (issue3088) (issue3155)...
r15658 if appdata:
path = os.path.join(appdata, longname, hash)
Benjamin Pollack
largefiles: use XDG and OS X-specific cache locations by default (issue3067)
r15320 elif platform.system() == 'Darwin':
Kevin Gessner
largefiles: don't require a user cache (issue3088) (issue3155)...
r15658 home = os.getenv('HOME')
if home:
path = os.path.join(home, 'Library', 'Caches',
longname, hash)
various
hgext: add largefiles extension...
r15168 elif os.name == 'posix':
Benjamin Pollack
largefiles: use XDG and OS X-specific cache locations by default (issue3067)
r15320 path = os.getenv('XDG_CACHE_HOME')
if path:
path = os.path.join(path, longname, hash)
else:
Kevin Gessner
largefiles: don't require a user cache (issue3088) (issue3155)...
r15658 home = os.getenv('HOME')
if home:
path = os.path.join(home, '.cache', longname, hash)
various
hgext: add largefiles extension...
r15168 else:
Greg Ward
largefiles: improve error reporting...
r15253 raise util.Abort(_('unknown operating system: %s\n') % os.name)
various
hgext: add largefiles extension...
r15168 return path
Benjamin Pollack
largefiles: rename functions and methods to match desired behavior...
r15316 def inusercache(ui, hash):
Kevin Gessner
largefiles: don't require a user cache (issue3088) (issue3155)...
r15658 path = usercachepath(ui, hash)
return path and os.path.exists(path)
various
hgext: add largefiles extension...
r15168
def findfile(repo, hash):
Benjamin Pollack
largefiles: rename functions and methods to match desired behavior...
r15316 if instore(repo, hash):
Martin Geisler
largefiles: lowercase messages
r16928 repo.ui.note(_('found %s in store\n') % hash)
Na'Tosha Bard
largefiles: refactor lfutil.findfiles to be more logical
r15913 return storepath(repo, hash)
Benjamin Pollack
largefiles: make the store primary, and the user cache secondary...
r15317 elif inusercache(repo.ui, hash):
Martin Geisler
largefiles: lowercase messages
r16928 repo.ui.note(_('found %s in system cache\n') % hash)
Hao Lian
largefiles: ensure destination directory exists before findfile links to there...
r15408 path = storepath(repo, hash)
link(usercachepath(repo.ui, hash), path)
Na'Tosha Bard
largefiles: refactor lfutil.findfiles to be more logical
r15913 return path
return None
various
hgext: add largefiles extension...
r15168
Na'Tosha Bard
largefiles: remove use of underscores that breaks coding convention
r16247 class largefilesdirstate(dirstate.dirstate):
various
hgext: add largefiles extension...
r15168 def __getitem__(self, key):
Na'Tosha Bard
largefiles: remove use of underscores that breaks coding convention
r16247 return super(largefilesdirstate, self).__getitem__(unixpath(key))
various
hgext: add largefiles extension...
r15168 def normal(self, f):
Na'Tosha Bard
largefiles: remove use of underscores that breaks coding convention
r16247 return super(largefilesdirstate, self).normal(unixpath(f))
various
hgext: add largefiles extension...
r15168 def remove(self, f):
Na'Tosha Bard
largefiles: remove use of underscores that breaks coding convention
r16247 return super(largefilesdirstate, self).remove(unixpath(f))
various
hgext: add largefiles extension...
r15168 def add(self, f):
Na'Tosha Bard
largefiles: remove use of underscores that breaks coding convention
r16247 return super(largefilesdirstate, self).add(unixpath(f))
various
hgext: add largefiles extension...
r15168 def drop(self, f):
Na'Tosha Bard
largefiles: remove use of underscores that breaks coding convention
r16247 return super(largefilesdirstate, self).drop(unixpath(f))
various
hgext: add largefiles extension...
r15168 def forget(self, f):
Na'Tosha Bard
largefiles: remove use of underscores that breaks coding convention
r16247 return super(largefilesdirstate, self).forget(unixpath(f))
Na'Tosha Bard
largefiles: correctly handle dirstate status when rebasing...
r15793 def normallookup(self, f):
Na'Tosha Bard
largefiles: remove use of underscores that breaks coding convention
r16247 return super(largefilesdirstate, self).normallookup(unixpath(f))
Mads Kiilerich
largefiles: simplify lfdirstate ignore handling - it is only for tracking .hglf
r18148 def _ignore(self):
return False
various
hgext: add largefiles extension...
r15168
Matt Harbison
largefiles: enable islfilesrepo() prior to a commit (issue3541)...
r17659 def openlfdirstate(ui, repo, create=True):
various
hgext: add largefiles extension...
r15168 '''
Greg Ward
largefiles: improve comments, internal docstrings...
r15252 Return a dirstate object that tracks largefiles: i.e. its root is
the repo root, but it is saved in .hg/largefiles/dirstate.
various
hgext: add largefiles extension...
r15168 '''
Mads Kiilerich
largefiles: rename 'admin' to more descriptive 'lfstoredir'
r18147 lfstoredir = repo.join(longname)
opener = scmutil.opener(lfstoredir)
Na'Tosha Bard
largefiles: remove use of underscores that breaks coding convention
r16247 lfdirstate = largefilesdirstate(opener, ui, repo.root,
Greg Ward
largefiles: drop more unnecessary compatibility checks
r15349 repo.dirstate._validate)
various
hgext: add largefiles extension...
r15168
Greg Ward
largefiles: improve comments, internal docstrings...
r15252 # If the largefiles dirstate does not exist, populate and create
# it. This ensures that we create it on the first meaningful
Levi Bard
largefiles: fix inappropriate locking (issue3182)...
r15794 # largefiles operation in a new clone.
Mads Kiilerich
largefiles: rename 'admin' to more descriptive 'lfstoredir'
r18147 if create and not os.path.exists(os.path.join(lfstoredir, 'dirstate')):
util.makedirs(lfstoredir)
various
hgext: add largefiles extension...
r15168 matcher = getstandinmatcher(repo)
Mads Kiilerich
largefiles: remove trivial portability wrappers
r18154 for standin in repo.dirstate.walk(matcher, [], False, False):
various
hgext: add largefiles extension...
r15168 lfile = splitstandin(standin)
lfdirstate.normallookup(lfile)
return lfdirstate
Na'Tosha Bard
largefiles: remove use of underscores that breaks coding convention
r16247 def lfdirstatestatus(lfdirstate, repo, rev):
Levi Bard
largefiles: fix inappropriate locking (issue3182)...
r15794 match = match_.always(repo.root, repo.getcwd())
s = lfdirstate.status(match, [], False, False, False)
unsure, modified, added, removed, missing, unknown, ignored, clean = s
for lfile in unsure:
Mads Kiilerich
largefiles: fix revert removing a largefile from a merge...
r18299 try:
fctx = repo[rev][standin(lfile)]
except LookupError:
fctx = None
if not fctx or fctx.data().strip() != hashfile(repo.wjoin(lfile)):
Levi Bard
largefiles: fix inappropriate locking (issue3182)...
r15794 modified.append(lfile)
else:
clean.append(lfile)
lfdirstate.normal(lfile)
various
hgext: add largefiles extension...
r15168 return (modified, added, removed, missing, unknown, ignored, clean)
def listlfiles(repo, rev=None, matcher=None):
Greg Ward
largefiles: improve comments, internal docstrings...
r15252 '''return a list of largefiles in the working copy or the
specified changeset'''
various
hgext: add largefiles extension...
r15168
if matcher is None:
matcher = getstandinmatcher(repo)
# ignore unknown files in working directory
Greg Ward
largefiles: cosmetics, whitespace, code style...
r15255 return [splitstandin(f)
for f in repo[rev].walk(matcher)
various
hgext: add largefiles extension...
r15168 if rev is not None or repo.dirstate[f] != '?']
Benjamin Pollack
largefiles: rename functions and methods to match desired behavior...
r15316 def instore(repo, hash):
return os.path.exists(storepath(repo, hash))
various
hgext: add largefiles extension...
r15168
Benjamin Pollack
largefiles: rename functions and methods to match desired behavior...
r15316 def storepath(repo, hash):
various
hgext: add largefiles extension...
r15168 return repo.join(os.path.join(longname, hash))
def copyfromcache(repo, hash, filename):
Greg Ward
largefiles: improve comments, internal docstrings...
r15252 '''Copy the specified largefile from the repo or system cache to
filename in the repository. Return true on success or false if the
file was not found in either cache (which should not happened:
this is meant to be called only after ensuring that the needed
largefile exists in the cache).'''
various
hgext: add largefiles extension...
r15168 path = findfile(repo, hash)
if path is None:
return False
util.makedirs(os.path.dirname(repo.wjoin(filename)))
Martin Geisler
largefiles: add comment about non-atomic working directory...
r15570 # The write may fail before the file is fully written, but we
# don't use atomic writes in the working copy.
various
hgext: add largefiles extension...
r15168 shutil.copy(path, repo.wjoin(filename))
return True
Benjamin Pollack
largefiles: rename functions and methods to match desired behavior...
r15316 def copytostore(repo, rev, file, uploaded=False):
Matt Harbison
largefiles: respect the rev when reading standins in copytostore() (issue3630)...
r17877 hash = readstandin(repo, file, rev)
Benjamin Pollack
largefiles: rename functions and methods to match desired behavior...
r15316 if instore(repo, hash):
various
hgext: add largefiles extension...
r15168 return
Benjamin Pollack
largefiles: rename functions and methods to match desired behavior...
r15316 copytostoreabsolute(repo, repo.wjoin(file), hash)
various
hgext: add largefiles extension...
r15168
Dan Villiom Podlaski Christiansen
largefiles: factor out a copyalltostore() function
r15796 def copyalltostore(repo, node):
'''Copy all largefiles in a given revision to the store'''
ctx = repo[node]
for filename in ctx.files():
if isstandin(filename) and filename in ctx.manifest():
realfile = splitstandin(filename)
copytostore(repo, ctx.node(), realfile)
Benjamin Pollack
largefiles: rename functions and methods to match desired behavior...
r15316 def copytostoreabsolute(repo, file, hash):
if inusercache(repo.ui, hash):
link(usercachepath(repo.ui, hash), storepath(repo, hash))
Matt Harbison
largefiles: don't copy largefiles from working dir to the store while converting...
r17878 elif not getattr(repo, "_isconverting", False):
Mads Kiilerich
largefiles: refactoring - create destination dir in lfutil.link
r18998 util.makedirs(os.path.dirname(storepath(repo, hash)))
Martin Geisler
largefiles: use repo.store.createmode for new files in .hg/largefiles...
r16153 dst = util.atomictempfile(storepath(repo, hash),
createmode=repo.store.createmode)
Matt Mackall
largefiles: copy files in binary mode (issue3164)
r15699 for chunk in util.filechunkiter(open(file, 'rb')):
Martin Geisler
largefiles: write .hg/largefiles/ files atomically...
r15571 dst.write(chunk)
dst.close()
Benjamin Pollack
largefiles: rename functions and methods to match desired behavior...
r15316 linktousercache(repo, hash)
various
hgext: add largefiles extension...
r15168
Benjamin Pollack
largefiles: rename functions and methods to match desired behavior...
r15316 def linktousercache(repo, hash):
Kevin Gessner
largefiles: don't require a user cache (issue3088) (issue3155)...
r15658 path = usercachepath(repo.ui, hash)
if path:
link(storepath(repo, hash), path)
various
hgext: add largefiles extension...
r15168
def getstandinmatcher(repo, pats=[], opts={}):
'''Return a match object that applies pats to the standin directory'''
Mads Kiilerich
largefiles: use plain wjoin instead of the complex pathto
r18150 standindir = repo.wjoin(shortname)
various
hgext: add largefiles extension...
r15168 if pats:
Mads Kiilerich
largefiles: fix commit when using relative paths from subdirectory...
r18490 pats = [os.path.join(standindir, pat) for pat in pats]
Mads Kiilerich
largefiles: getstandinmatcher should not depend on existence of directories...
r18724 else:
various
hgext: add largefiles extension...
r15168 # no patterns: relative to repo root
pats = [standindir]
Mads Kiilerich
largefiles: simplify lfutil.getstandinmatcher by inlining getmatcher
r18146 # no warnings about missing files or directories
Na'Tosha Bard
largefiles: remove pre-1.9 code from extension first bundled with 1.9
r15224 match = scmutil.match(repo[None], pats, opts)
Mads Kiilerich
largefiles: simplify lfutil.getstandinmatcher by inlining getmatcher
r18146 match.bad = lambda f, msg: None
various
hgext: add largefiles extension...
r15168 return match
def composestandinmatcher(repo, rmatcher):
Greg Ward
largefiles: improve comments, internal docstrings...
r15252 '''Return a matcher that accepts standins corresponding to the
files accepted by rmatcher. Pass the list of files in the matcher
as the paths specified by the user.'''
various
hgext: add largefiles extension...
r15168 smatcher = getstandinmatcher(repo, rmatcher.files())
isstandin = smatcher.matchfn
Na'Tosha Bard
largefiles: remove use of underscores that breaks coding convention
r16247 def composedmatchfn(f):
various
hgext: add largefiles extension...
r15168 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
Na'Tosha Bard
largefiles: remove use of underscores that breaks coding convention
r16247 smatcher.matchfn = composedmatchfn
various
hgext: add largefiles extension...
r15168
return smatcher
def standin(filename):
'''Return the repo-relative path to the standin for the specified big
file.'''
# Notes:
Mads Kiilerich
fix wording and not-completely-trivial spelling errors and bad docstrings
r17425 # 1) Some callers want an absolute path, but for instance addlargefiles
Mads Kiilerich
largefiles: remove trivial portability wrappers
r18154 # needs it repo-relative so it can be passed to repo[None].add(). So
# leave it up to the caller to use repo.wjoin() to get an absolute path.
various
hgext: add largefiles extension...
r15168 # 2) Join with '/' because that's what dirstate always uses, even on
# Windows. Change existing separator to '/' first in case we are
# passed filenames from an external source (like the command line).
Mads Kiilerich
largefiles: use constant for '.hglf/'...
r18151 return shortnameslash + util.pconvert(filename)
various
hgext: add largefiles extension...
r15168
def isstandin(filename):
Greg Ward
largefiles: improve comments, internal docstrings...
r15252 '''Return true if filename is a big file standin. filename must be
in Mercurial's internal form (slash-separated).'''
Mads Kiilerich
largefiles: use constant for '.hglf/'...
r18151 return filename.startswith(shortnameslash)
various
hgext: add largefiles extension...
r15168
def splitstandin(filename):
# Split on / because that's what dirstate always uses, even on Windows.
# Change local separator to / first just in case we are passed filenames
# from an external source (like the command line).
FUJIWARA Katsunori
i18n: use util.pconvert() instead of 'str.replace()' for problematic encoding...
r16066 bits = util.pconvert(filename).split('/', 1)
various
hgext: add largefiles extension...
r15168 if len(bits) == 2 and bits[0] == shortname:
return bits[1]
else:
return None
def updatestandin(repo, standin):
file = repo.wjoin(splitstandin(standin))
if os.path.exists(file):
hash = hashfile(file)
executable = getexecutable(file)
writestandin(repo, standin, hash, executable)
def readstandin(repo, filename, node=None):
'''read hex hash from standin for filename at given node, or working
directory if no node is given'''
return repo[node][standin(filename)].data().strip()
def writestandin(repo, standin, hash, executable):
Greg Ward
largefiles: improve comments, internal docstrings...
r15252 '''write hash to <repo.root>/<standin>'''
Mads Kiilerich
largefiles: use repo.wwrite for writing standins (issue3909)
r19089 repo.wwrite(standin, hash + '\n', executable and 'x' or '')
various
hgext: add largefiles extension...
r15168
def copyandhash(instream, outfile):
'''Read bytes from instream (iterable) and write them to outfile,
Mads Kiilerich
largefiles: remove blecch from lfutil.copyandhash - don't close the passed fd
r19002 computing the SHA-1 hash of the data along the way. Return the hash.'''
various
hgext: add largefiles extension...
r15168 hasher = util.sha1('')
for data in instream:
hasher.update(data)
outfile.write(data)
Mads Kiilerich
largefiles: refactoring - return hex from _getfile and copyandhash
r18999 return hasher.hexdigest()
various
hgext: add largefiles extension...
r15168
def hashrepofile(repo, file):
return hashfile(repo.wjoin(file))
def hashfile(file):
if not os.path.exists(file):
return ''
hasher = util.sha1('')
fd = open(file, 'rb')
Mads Kiilerich
largefiles: drop lfutil.blockstream - use filechunkiter like everybody else...
r19001 for data in util.filechunkiter(fd, 128 * 1024):
various
hgext: add largefiles extension...
r15168 hasher.update(data)
fd.close()
return hasher.hexdigest()
def getexecutable(filename):
mode = os.stat(filename).st_mode
Greg Ward
largefiles: cosmetics, whitespace, code style...
r15255 return ((mode & stat.S_IXUSR) and
(mode & stat.S_IXGRP) and
(mode & stat.S_IXOTH))
various
hgext: add largefiles extension...
r15168
def urljoin(first, second, *arg):
def join(left, right):
if not left.endswith('/'):
left += '/'
if right.startswith('/'):
right = right[1:]
return left + right
url = join(first, second)
for a in arg:
url = join(url, a)
return url
def hexsha1(data):
"""hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
object data"""
Thomas Arendsen Hein
largefiles: use util.sha1() instead of hashlib.sha1() everywhere
r15347 h = util.sha1()
various
hgext: add largefiles extension...
r15168 for chunk in util.filechunkiter(data):
h.update(chunk)
return h.hexdigest()
def httpsendfile(ui, filename):
Na'Tosha Bard
largefiles: remove pre-1.9 code from extension first bundled with 1.9
r15224 return httpconnection.httpsendfile(ui, filename, 'rb')
various
hgext: add largefiles extension...
r15168
def unixpath(path):
Greg Ward
largefiles: improve comments, internal docstrings...
r15252 '''Return a version of path normalized for use with the lfdirstate.'''
FUJIWARA Katsunori
i18n: use util.pconvert() instead of 'str.replace()' for problematic encoding...
r16066 return util.pconvert(os.path.normpath(path))
various
hgext: add largefiles extension...
r15168
def islfilesrepo(repo):
Matt Harbison
largefiles: enable islfilesrepo() prior to a commit (issue3541)...
r17659 if ('largefiles' in repo.requirements and
Mads Kiilerich
largefiles: use constant for '.hglf/'...
r18151 util.any(shortnameslash in f[0] for f in repo.store.datafiles())):
Matt Harbison
largefiles: enable islfilesrepo() prior to a commit (issue3541)...
r17659 return True
return util.any(openlfdirstate(repo.ui, repo, False))
various
hgext: add largefiles extension...
r15168
Matt Mackall
largefiles: py2.4 doesn't have BaseException...
r15333 class storeprotonotcapable(Exception):
various
hgext: add largefiles extension...
r15168 def __init__(self, storetypes):
self.storetypes = storetypes
Na'Tosha Bard
largefiles: only cache largefiles in new heads...
r16103
Na'Tosha Bard
largefiles: optimize update speed by only updating changed largefiles...
r16120 def getstandinsstate(repo):
standins = []
matcher = getstandinmatcher(repo)
Mads Kiilerich
largefiles: remove trivial portability wrappers
r18154 for standin in repo.dirstate.walk(matcher, [], False, False):
Na'Tosha Bard
largefiles: optimize update speed by only updating changed largefiles...
r16120 lfile = splitstandin(standin)
Mads Kiilerich
largefiles: fix update from a merge with removed files...
r18300 try:
hash = readstandin(repo, lfile)
except IOError:
hash = None
standins.append((lfile, hash))
Na'Tosha Bard
largefiles: optimize update speed by only updating changed largefiles...
r16120 return standins
Na'Tosha Bard
largefiles: move calculation of largefiles for updating to utility function
r16245
def getlfilestoupdate(oldstandins, newstandins):
changedstandins = set(oldstandins).symmetric_difference(set(newstandins))
filelist = []
for f in changedstandins:
if f[0] not in filelist:
filelist.append(f[0])
return filelist