1# logexchange.py 2# 3# Copyright 2017 Augie Fackler <raf@durin42.com> 4# Copyright 2017 Sean Farley <sean@farley.io> 5# 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. 8 9from __future__ import absolute_import 10 11from .node import hex 12 13from . import ( 14 pycompat, 15 util, 16 vfs as vfsmod, 17) 18from .utils import ( 19 urlutil, 20) 21 22# directory name in .hg/ in which remotenames files will be present 23remotenamedir = b'logexchange' 24 25 26def readremotenamefile(repo, filename): 27 """ 28 reads a file from .hg/logexchange/ directory and yields it's content 29 filename: the file to be read 30 yield a tuple (node, remotepath, name) 31 """ 32 33 vfs = vfsmod.vfs(repo.vfs.join(remotenamedir)) 34 if not vfs.exists(filename): 35 return 36 f = vfs(filename) 37 lineno = 0 38 for line in f: 39 line = line.strip() 40 if not line: 41 continue 42 # contains the version number 43 if lineno == 0: 44 lineno += 1 45 try: 46 node, remote, rname = line.split(b'\0') 47 yield node, remote, rname 48 except ValueError: 49 pass 50 51 f.close() 52 53 54def readremotenames(repo): 55 """ 56 read the details about the remotenames stored in .hg/logexchange/ and 57 yields a tuple (node, remotepath, name). It does not yields information 58 about whether an entry yielded is branch or bookmark. To get that 59 information, call the respective functions. 60 """ 61 62 for bmentry in readremotenamefile(repo, b'bookmarks'): 63 yield bmentry 64 for branchentry in readremotenamefile(repo, b'branches'): 65 yield branchentry 66 67 68def writeremotenamefile(repo, remotepath, names, nametype): 69 vfs = vfsmod.vfs(repo.vfs.join(remotenamedir)) 70 f = vfs(nametype, b'w', atomictemp=True) 71 # write the storage version info on top of file 72 # version '0' represents the very initial version of the storage format 73 f.write(b'0\n\n') 74 75 olddata = set(readremotenamefile(repo, nametype)) 76 # re-save the data from a different remote than this one. 77 for node, oldpath, rname in sorted(olddata): 78 if oldpath != remotepath: 79 f.write(b'%s\0%s\0%s\n' % (node, oldpath, rname)) 80 81 for name, node in sorted(pycompat.iteritems(names)): 82 if nametype == b"branches": 83 for n in node: 84 f.write(b'%s\0%s\0%s\n' % (n, remotepath, name)) 85 elif nametype == b"bookmarks": 86 if node: 87 f.write(b'%s\0%s\0%s\n' % (node, remotepath, name)) 88 89 f.close() 90 91 92def saveremotenames(repo, remotepath, branches=None, bookmarks=None): 93 """ 94 save remotenames i.e. remotebookmarks and remotebranches in their 95 respective files under ".hg/logexchange/" directory. 96 """ 97 wlock = repo.wlock() 98 try: 99 if bookmarks: 100 writeremotenamefile(repo, remotepath, bookmarks, b'bookmarks') 101 if branches: 102 writeremotenamefile(repo, remotepath, branches, b'branches') 103 finally: 104 wlock.release() 105 106 107def activepath(repo, remote): 108 """returns remote path""" 109 # is the remote a local peer 110 local = remote.local() 111 112 # determine the remote path from the repo, if possible; else just 113 # use the string given to us 114 rpath = remote 115 if local: 116 rpath = util.pconvert(remote._repo.root) 117 elif not isinstance(remote, bytes): 118 rpath = remote._url 119 120 # represent the remotepath with user defined path name if exists 121 for path, url in repo.ui.configitems(b'paths'): 122 # remove auth info from user defined url 123 noauthurl = urlutil.removeauth(url) 124 125 # Standardize on unix style paths, otherwise some {remotenames} end up 126 # being an absolute path on Windows. 127 url = util.pconvert(bytes(url)) 128 noauthurl = util.pconvert(noauthurl) 129 if url == rpath or noauthurl == rpath: 130 rpath = path 131 break 132 133 return rpath 134 135 136def pullremotenames(localrepo, remoterepo): 137 """ 138 pulls bookmarks and branches information of the remote repo during a 139 pull or clone operation. 140 localrepo is our local repository 141 remoterepo is the peer instance 142 """ 143 remotepath = activepath(localrepo, remoterepo) 144 145 with remoterepo.commandexecutor() as e: 146 bookmarks = e.callcommand( 147 b'listkeys', 148 { 149 b'namespace': b'bookmarks', 150 }, 151 ).result() 152 153 # on a push, we don't want to keep obsolete heads since 154 # they won't show up as heads on the next pull, so we 155 # remove them here otherwise we would require the user 156 # to issue a pull to refresh the storage 157 bmap = {} 158 repo = localrepo.unfiltered() 159 160 with remoterepo.commandexecutor() as e: 161 branchmap = e.callcommand(b'branchmap', {}).result() 162 163 for branch, nodes in pycompat.iteritems(branchmap): 164 bmap[branch] = [] 165 for node in nodes: 166 if node in repo and not repo[node].obsolete(): 167 bmap[branch].append(hex(node)) 168 169 saveremotenames(localrepo, remotepath, bmap, bookmarks) 170