Mercurial > hg
view hgext/inotify/client.py @ 18520:751135cca13c stable
subrepo: allows to drop courtesy phase sync (issue3781)
Publishing server may contains draft changeset when they are created locally. As
publishing is the default, it is actually fairly common. Because of this
"inconsistency" phases synchronization may be done even to publishing server.
This may cause severe issues for subrepo. It is possible to reference read-only
repository as subrepo. Push in a super repo recursively push subrepo. Those
pushes to potential read only repo are not optional, they are "suffered" not
"choosed". This does not break because as the repo is untouched the push is
supposed to be empty. If the reference repo locally contains draft changesets, a
courtesy push is triggered to turn them public. As the repo is read only, the
push fails (after possible prompt asking for credential). Failure of the
sub-push aborts the whole subrepo push. This force the user to define a custom
default-push for such subrepo.
This changeset introduce a prevention of this error client side by skipping the
courtesy phase synchronisation in problematic situation. The phases
synchronisation is skipped when four conditions are gathered:
- this is a subrepo push, (normal push to read-only repo)
- and remote support phase
- and remote is publishing
- and no changesets was pushed (if we pushed changesets, repo is not read only)
The internal config option used in this version is not definitive. It is here to
demonstrate a working fix to the issue.
In the future we probably wants to track subrepo changes and avoid pushing to
untouched one. That will prevent any attempt to push to read-only or unreachable
subrepo.
Another fix to prevent courtesy push from older clients to push to newer server
is also still needed.
author | Pierre-Yves David <pierre-yves.david@logilab.fr> |
---|---|
date | Thu, 31 Jan 2013 01:44:29 +0100 |
parents | 516b000fbb7e |
children | 10103caea69d |
line wrap: on
line source
# client.py - inotify status client # # Copyright 2006, 2007, 2008 Bryan O'Sullivan <bos@serpentine.com> # Copyright 2007, 2008 Brendan Cully <brendan@kublai.com> # Copyright 2009 Nicolas Dumazet <nicdumz@gmail.com> # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. from mercurial.i18n import _ import common, server import errno, os, socket, struct class QueryFailed(Exception): pass def start_server(function): """ Decorator. Tries to call function, if it fails, try to (re)start inotify server. Raise QueryFailed if something went wrong """ def decorated_function(self, *args): try: return function(self, *args) except (OSError, socket.error), err: autostart = self.ui.configbool('inotify', 'autostart', True) if err.args[0] == errno.ECONNREFUSED: self.ui.warn(_('inotify-client: found dead inotify server ' 'socket; removing it\n')) os.unlink(os.path.join(self.root, '.hg', 'inotify.sock')) if err.args[0] in (errno.ECONNREFUSED, errno.ENOENT) and autostart: try: try: server.start(self.ui, self.dirstate, self.root, dict(daemon=True, daemon_pipefds='')) except server.AlreadyStartedException, inst: # another process may have started its own # inotify server while this one was starting. self.ui.debug(str(inst)) except Exception, inst: self.ui.warn(_('inotify-client: could not start inotify ' 'server: %s\n') % inst) else: try: return function(self, *args) except socket.error, err: self.ui.warn(_('inotify-client: could not talk to new ' 'inotify server: %s\n') % err.args[-1]) elif err.args[0] in (errno.ECONNREFUSED, errno.ENOENT): # silently ignore normal errors if autostart is False self.ui.debug('(inotify server not running)\n') else: self.ui.warn(_('inotify-client: failed to contact inotify ' 'server: %s\n') % err.args[-1]) self.ui.traceback() raise QueryFailed('inotify query failed') return decorated_function class client(object): def __init__(self, ui, repo): self.ui = ui self.dirstate = repo.dirstate self.root = repo.root self.sock = socket.socket(socket.AF_UNIX) def _connect(self): sockpath = os.path.join(self.root, '.hg', 'inotify.sock') try: self.sock.connect(sockpath) except socket.error, err: if err.args[0] == "AF_UNIX path too long": sockpath = os.readlink(sockpath) self.sock.connect(sockpath) else: raise def _send(self, type, data): """Sends protocol version number, and the data""" self.sock.sendall(chr(common.version) + type + data) self.sock.shutdown(socket.SHUT_WR) def _receive(self, type): """ Read data, check version number, extract headers, and returns a tuple (data descriptor, header) Raises QueryFailed on error """ cs = common.recvcs(self.sock) try: version = ord(cs.read(1)) except TypeError: # empty answer, assume the server crashed self.ui.warn(_('inotify-client: received empty answer from inotify ' 'server')) raise QueryFailed('server crashed') if version != common.version: self.ui.warn(_('(inotify: received response from incompatible ' 'server version %d)\n') % version) raise QueryFailed('incompatible server version') readtype = cs.read(4) if readtype != type: self.ui.warn(_('(inotify: received \'%s\' response when expecting' ' \'%s\')\n') % (readtype, type)) raise QueryFailed('wrong response type') hdrfmt = common.resphdrfmts[type] hdrsize = common.resphdrsizes[type] try: resphdr = struct.unpack(hdrfmt, cs.read(hdrsize)) except struct.error: raise QueryFailed('unable to retrieve query response headers') return cs, resphdr def query(self, type, req): self._connect() self._send(type, req) return self._receive(type) @start_server def statusquery(self, names, match, ignored, clean, unknown=True): def genquery(): for n in names: yield n states = 'almrx!' if ignored: raise ValueError('this is insanity') if clean: states += 'c' if unknown: states += '?' yield states req = '\0'.join(genquery()) cs, resphdr = self.query('STAT', req) def readnames(nbytes): if nbytes: names = cs.read(nbytes) if names: return filter(match, names.split('\0')) return [] results = tuple(map(readnames, resphdr[:-1])) if names: nbytes = resphdr[-1] vdirs = cs.read(nbytes) if vdirs: for vdir in vdirs.split('\0'): match.dir(vdir) return results @start_server def debugquery(self): cs, resphdr = self.query('DBUG', '') nbytes = resphdr[0] names = cs.read(nbytes) return names.split('\0')