diff --git a/infinitepush/__init__.py b/infinitepush/__init__.py --- a/infinitepush/__init__.py +++ b/infinitepush/__init__.py @@ -324,6 +324,7 @@ smartlogmod = extensions.find('smartlog') wrapcommand(smartlogmod.cmdtable, 'smartlog', _smartlog) extensions.afterloaded('smartlog', wrapsmartlog) + backupcommands.extsetup(ui) def _smartlog(orig, ui, repo, **opts): res = orig(ui, repo, **opts) diff --git a/infinitepush/backupcommands.py b/infinitepush/backupcommands.py --- a/infinitepush/backupcommands.py +++ b/infinitepush/backupcommands.py @@ -4,6 +4,11 @@ # GNU General Public License version 2 or any later version. """ [infinitepushbackup] + # Whether to enable automatic backups. If this option is True then a backup + # process will be started after every mercurial command that modifies the + # repo, for example, commit, amend, histedit, rebase etc. + autobackup = False + # path to the directory where pushback logs should be stored logdir = path/to/dir @@ -53,9 +58,12 @@ changegroup, commands, discovery, + dispatch, encoding, error, + extensions, hg, + localrepo, lock as lockmod, phases, registrar, @@ -98,6 +106,13 @@ _backuplockname = 'infinitepushbackup.lock' +def extsetup(ui): + if ui.configbool('infinitepushbackup', 'autobackup', False): + extensions.wrapfunction(dispatch, 'runcommand', + _autobackupruncommandwrapper) + extensions.wrapfunction(localrepo.localrepository, 'transaction', + _transaction) + @command('pushbackup', [('', 'background', None, 'run backup in background')]) def backup(ui, repo, dest=None, **opts): @@ -112,48 +127,7 @@ """ if opts.get('background'): - background_cmd = ['hg', 'pushbackup'] - if dest: - background_cmd.append(dest) - logfile = None - logdir = ui.config('infinitepushbackup', 'logdir') - if logdir: - # make newly created files and dirs non-writable - oldumask = os.umask(0o022) - try: - try: - username = util.shortuser(ui.username()) - except Exception: - username = 'unknown' - - if not _checkcommonlogdir(logdir): - raise WrongPermissionsException(logdir) - - userlogdir = os.path.join(logdir, username) - util.makedirs(userlogdir) - - if not _checkuserlogdir(userlogdir): - raise WrongPermissionsException(userlogdir) - - reporoot = repo.origroot - reponame = os.path.basename(reporoot) - _removeoldlogfiles(userlogdir, reponame) - logfile = _getlogfilename(logdir, username, reponame) - except (OSError, IOError) as e: - ui.debug('infinitepush backup log is disabled: %s\n' % e) - except WrongPermissionsException as e: - ui.debug(('%s directory has incorrect permission, ' + - 'infinitepush backup logging will be disabled\n') % - e.logdir) - finally: - os.umask(oldumask) - - if not logfile: - logfile = os.devnull - - with open(logfile, 'a') as f: - subprocess.Popen(background_cmd, shell=False, stdout=f, - stderr=subprocess.STDOUT) + _dobackgroundbackup(ui, repo, dest) return 0 try: @@ -355,6 +329,34 @@ ui.warn(_('Run `hg pushbackup` to perform a backup. If this fails,\n' 'please report to the Source Control @ FB group.\n')) +def _autobackupruncommandwrapper(orig, lui, repo, cmd, fullargs, *args): + ''' + If this wrapper is enabled then auto backup is started after every command + that modifies a repository. + Since we don't want to start auto backup after read-only commands, + then this wrapper checks if this command opened at least one transaction. + If yes then background backup will be started. + ''' + + # For chg, do not wrap the "serve" runcommand call + if 'CHGINTERNALMARK' in os.environ: + return orig(lui, repo, cmd, fullargs, *args) + + try: + return orig(lui, repo, cmd, fullargs, *args) + finally: + if getattr(repo, 'txnwasopened', False): + _dobackgroundbackup(lui, repo) + +def _transaction(orig, self, *args, **kwargs): + ''' Wrapper that records if a transaction was opened. + + If a transaction was opened then we want to start background backup process. + This hook records the fact that transaction was opened. + ''' + self.txnwasopened = True + return orig(self, *args, **kwargs) + def _backupheads(ui, repo): """Returns the set of heads that should be backed up in this repo.""" maxheadstobackup = ui.configint('infinitepushbackup', @@ -455,6 +457,50 @@ unwrapfunction(changegroup.cg2packer, 'deltaparent', _deltaparent) return 0 +def _dobackgroundbackup(ui, repo, dest=None): + background_cmd = ['hg', 'pushbackup'] + if dest: + background_cmd.append(dest) + logfile = None + logdir = ui.config('infinitepushbackup', 'logdir') + if logdir: + # make newly created files and dirs non-writable + oldumask = os.umask(0o022) + try: + try: + username = util.shortuser(ui.username()) + except Exception: + username = 'unknown' + + if not _checkcommonlogdir(logdir): + raise WrongPermissionsException(logdir) + + userlogdir = os.path.join(logdir, username) + util.makedirs(userlogdir) + + if not _checkuserlogdir(userlogdir): + raise WrongPermissionsException(userlogdir) + + reporoot = repo.origroot + reponame = os.path.basename(reporoot) + _removeoldlogfiles(userlogdir, reponame) + logfile = _getlogfilename(logdir, username, reponame) + except (OSError, IOError) as e: + ui.debug('infinitepush backup log is disabled: %s\n' % e) + except WrongPermissionsException as e: + ui.debug(('%s directory has incorrect permission, ' + + 'infinitepush backup logging will be disabled\n') % + e.logdir) + finally: + os.umask(oldumask) + + if not logfile: + logfile = os.devnull + + with open(logfile, 'a') as f: + subprocess.Popen(background_cmd, shell=False, stdout=f, + stderr=subprocess.STDOUT) + def _dobackupcheck(bkpstate, ui, repo, dest, **opts): remotehexnodes = sorted( set(bkpstate.heads).union(bkpstate.localbookmarks.values())) diff --git a/tests/library-infinitepush.sh b/tests/library-infinitepush.sh --- a/tests/library-infinitepush.sh +++ b/tests/library-infinitepush.sh @@ -96,3 +96,9 @@ sleep 1 hg debugwaitbackup } + +mkcommitautobackup() { + echo $1 > $1 + hg add $1 + hg ci -m $1 --config infinitepushbackup.autobackup=True +} diff --git a/tests/test-infinitepush-backup.t b/tests/test-infinitepush-backup.t --- a/tests/test-infinitepush-backup.t +++ b/tests/test-infinitepush-backup.t @@ -1,3 +1,8 @@ + + $ cat >> $HGRCPATH << EOF + > [extensions] + > drawdag=$RUNTESTDIR/drawdag.py + > EOF $ setup() { > cat << EOF >> .hg/hgrc @@ -138,8 +143,7 @@ infinitepush/backups/test/[0-9a-zA-Z.-]+\$TESTTMP/client/heads/667453c0787e7830fdfb86db0f8c29aa7af2a1ea 667453c0787e7830fdfb86db0f8c29aa7af2a1ea (re) infinitepush/backups/test/[0-9a-zA-Z.-]+\$TESTTMP/client/heads/d5609f7fa63352da538eeffbe3ffabed1779aafc d5609f7fa63352da538eeffbe3ffabed1779aafc (re) infinitepush/backups/test/[0-9a-zA-Z.-]+\$TESTTMP/client/heads/f79c5017def3b9af9928edbb52cc620c74b4b291 f79c5017def3b9af9928edbb52cc620c74b4b291 (re) - $ mkcommit newcommit - $ hg pushbackup --background + $ mkcommitautobackup newcommit $ waitbgbackup $ scratchbookmarks infinitepush/backups/test/[0-9a-zA-Z.-]+\$TESTTMP/client/heads/3a30e220fe42e969e34bbe8001b951a20f31f2e8 3a30e220fe42e969e34bbe8001b951a20f31f2e8 (re) @@ -515,3 +519,82 @@ $ cat .hg/infinitepushlatestbackupinfo backuprevision=(\d+) (re) backuptime=(\d+) (re) + +Run command that creates multiple transactions. Make sure that just one backup is started + $ cd .. + $ rm -rf client + $ hg clone ssh://user@dummy/repo client -q + $ cd client + $ setup + $ hg debugdrawdag <<'EOS' + > C + > | + > B D + > |/ + > A + > EOS + $ hg log -r ':' -G -T '{desc} {node}' + o C 26805aba1e600a82e93661149f2313866a221a7b + | + | o D b18e25de2cf5fc4699a029ed635882849e53ef73 + | | + o | B 112478962961147124edd43549aedd1a335e44bf + |/ + o A 426bada5c67598ca65036d57d9e4b64b0c1ce7a0 + + @ initial 630839011471e17f808b92ab084bedfaca33b110 + + +Create logs directory and set correct permissions + $ mkdir $TESTTMP/logs + $ chmod 0755 $TESTTMP/logs + $ chmod +t $TESTTMP/logs + + $ hg pushbackup --config infinitepushbackup.logdir=$TESTTMP/logs + starting backup .* (re) + searching for changes + remote: pushing 4 commits: + remote: 426bada5c675 A + remote: 112478962961 B + remote: b18e25de2cf5 D + remote: 26805aba1e60 C + finished in \d+\.(\d+)? seconds (re) + $ hg isbackedup -r ':' + 630839011471e17f808b92ab084bedfaca33b110 not backed up + 426bada5c67598ca65036d57d9e4b64b0c1ce7a0 backed up + 112478962961147124edd43549aedd1a335e44bf backed up + b18e25de2cf5fc4699a029ed635882849e53ef73 backed up + 26805aba1e600a82e93661149f2313866a221a7b backed up + $ hg rebase -s B -d D --config infinitepushbackup.autobackup=True --config infinitepushbackup.logdir=$TESTTMP/logs + rebasing 2:112478962961 "B" (B) + rebasing 4:26805aba1e60 "C" (C tip) + $ waitbgbackup + $ hg log -r ':' -G -T '{desc} {node}' + o C ffeec75ec60331057b875fc5356c57c3ff204500 + | + o B 1ef11233b74dfa8b57e8285fd6f546096af8f4c2 + | + | x C 26805aba1e600a82e93661149f2313866a221a7b + | | + o | D b18e25de2cf5fc4699a029ed635882849e53ef73 + | | + | x B 112478962961147124edd43549aedd1a335e44bf + |/ + o A 426bada5c67598ca65036d57d9e4b64b0c1ce7a0 + + @ initial 630839011471e17f808b92ab084bedfaca33b110 + + $ hg isbackedup -r 'ffeec75ec + 1ef11233b7' + ffeec75ec60331057b875fc5356c57c3ff204500 backed up + 1ef11233b74dfa8b57e8285fd6f546096af8f4c2 backed up + +Check the logs, make sure just one process was started + $ cat $TESTTMP/logs/test/* + starting backup .* (re) + searching for changes + remote: pushing 4 commits: + remote: 426bada5c675 A + remote: b18e25de2cf5 D + remote: 1ef11233b74d B + remote: ffeec75ec603 C + finished in \d+\.(\d+)? seconds (re)