Index: src/supervisor/options.py
===================================================================
--- src/supervisor/options.py	(revision 812)
+++ src/supervisor/options.py	(working copy)
@@ -471,27 +471,11 @@
 
         self.identifier = section.identifier
 
-    def diff_process_groups(self, new):
-        cur = self.process_group_configs
-
-        curdict = dict(zip([cfg.name for cfg in cur], cur))
-        newdict = dict(zip([cfg.name for cfg in new], new))
-
-        added   = [cand for cand in new if cand.name not in curdict]
-        removed = [cand for cand in cur if cand.name not in newdict]
-
-        changed = [cand for cand in new
-                   if cand != curdict.get(cand.name, cand)]
-
-        return added, changed, removed
-
     def process_config_file(self):
         Options.process_config_file(self)
 
         new = self.configroot.supervisord.process_group_configs
-        changes = self.diff_process_groups(new)
         self.process_group_configs = new
-        return changes
 
     def read_config(self, fp):
         section = self.configroot.supervisord
Index: src/supervisor/rpcinterface.py
===================================================================
--- src/supervisor/rpcinterface.py	(revision 812)
+++ src/supervisor/rpcinterface.py	(working copy)
@@ -162,6 +162,21 @@
         self.supervisord.options.mood = SupervisorStates.RESTARTING
         return True
 
+    def reloadConfig(self):
+        """
+        Reload configuration
+
+        @return boolean result  always return True unless error
+        """
+        self._update('reloadConfig')
+        self.supervisord.options.process_config_file()
+        added, changed, removed = self.supervisord.diff_to_active()
+
+        added = [group.name for group in added]
+        changed = [group.name for group in changed]
+        removed = [group.name for group in removed]
+        return [[added, changed, removed]] # cannot return len > 1, apparently
+
     def addProcessGroup(self, name):
         """ Update the config for a running process from config file.
 
@@ -433,6 +448,29 @@
         killall.rpcinterface = self
         return killall # deferred
 
+    def getAllConfigInfo(self):
+        """ Get info about all availible process configurations. Each record
+        represents a single process (i.e. groups get flattened).
+
+        @return array result  An array of process config info records
+        """
+        self._update('getAllConfigInfo')
+
+        configinfo = []
+        for gconfig in self.supervisord.options.process_group_configs:
+            inuse = gconfig.name in self.supervisord.process_groups
+            for pconfig in gconfig.process_configs:
+                configinfo.append(
+                    { 'name': pconfig.name,
+                      'group': gconfig.name,
+                      'inuse': inuse,
+                      'autostart': pconfig.autostart,
+                      'group_prio': gconfig.priority,
+                      'process_prio': pconfig.priority })
+
+        configinfo.sort()
+        return configinfo
+
     def _interpretProcessInfo(self, info):
         state = info['state']
 
Index: src/supervisor/tests/test_options.py
===================================================================
--- src/supervisor/tests/test_options.py	(revision 812)
+++ src/supervisor/tests/test_options.py	(working copy)
@@ -405,94 +405,6 @@
         self.assertTrue(section.process_group_configs is
                         instance.process_group_configs)
 
-    def test_diff_add_remove(self):
-        options = self._makeOne()
-
-        pconfig = DummyPConfig(options, 'process1', 'process1')
-        group1 = DummyPGroupConfig(options, 'group1', pconfigs=[pconfig])
-
-        pconfig = DummyPConfig(options, 'process2', 'process2')
-        group2 = DummyPGroupConfig(options, 'group2', pconfigs=[pconfig])
-
-        new = [group1, group2]
-
-        added, changed, removed = options.diff_process_groups([])
-        self.assertEqual(added, options.process_group_configs)
-        self.assertEqual(removed, [])
-
-        options.process_group_configs = list(new)
-        added, changed, removed = options.diff_process_groups(new)
-        self.assertEqual(added, [])
-        self.assertEqual(changed, [])
-        self.assertEqual(removed, [])
-
-        pconfig = DummyPConfig(options, 'process3', 'process3')
-        new_group1 = DummyPGroupConfig(options, pconfigs=[pconfig])
-
-        pconfig = DummyPConfig(options, 'process4', 'process4')
-        new_group2 = DummyPGroupConfig(options, pconfigs=[pconfig])
-
-        new = [group2, new_group1, new_group2]
-
-        added, changed, removed = options.diff_process_groups(new)
-        self.assertEqual(added, [new_group1, new_group2])
-        self.assertEqual(changed, [])
-        self.assertEqual(removed, [group1])
-
-    def test_diff_changed(self):
-        from supervisor.options import ProcessConfig, ProcessGroupConfig
-
-        options = self._makeOne()
-
-        def make_pconfig(name, command, **params):
-            result = {
-                'name': name, 'command': command,
-                'directory': None, 'umask': None, 'priority': 999, 'autostart': True,
-                'autorestart': True, 'startsecs': 10, 'startretries': 999,
-                'uid': None, 'stdout_logfile': None, 'stdout_capture_maxbytes': 0,
-                'stdout_logfile_backups': 0, 'stdout_logfile_maxbytes': 0,
-                'stderr_logfile': None, 'stderr_capture_maxbytes': 0,
-                'stderr_logfile_backups': 0, 'stderr_logfile_maxbytes': 0,
-                'redirect_stderr': False,
-                'stopsignal': None, 'stopwaitsecs': 10,
-                'exitcodes': (0,2), 'environment': None, 'serverurl': None }
-            result.update(params)
-            return ProcessConfig(options, **result)
-
-        def make_gconfig(name, pconfigs):
-            return ProcessGroupConfig(options, name, 25, pconfigs)
-
-        pconfig = make_pconfig('process1', 'process1', uid='new')
-        group1 = make_gconfig('group1', [pconfig])
-
-        pconfig = make_pconfig('process2', 'process2')
-        group2 = make_gconfig('group2', [pconfig])
-        new = [group1, group2]
-
-        pconfig = make_pconfig('process1', 'process1', uid='old')
-        group3 = make_gconfig('group1', [pconfig])
-
-        pconfig = make_pconfig('process2', 'process2')
-        group4 = make_gconfig('group2', [pconfig])
-        options.process_group_configs = [group4, group3]
-
-        added, changed, removed = options.diff_process_groups(new)
-
-        self.assertEqual([added, removed], [[], []])
-        self.assertEqual(changed, [group1])
-
-        options = self._makeOne()
-        pconfig1 = make_pconfig('process1', 'process1')
-        pconfig2 = make_pconfig('process2', 'process2')
-        group1 = make_gconfig('group1', [pconfig1, pconfig2])
-        new = [group1]
-
-        options.process_group_configs = [make_gconfig('group1', [pconfig1])]
-
-        added, changed, removed = options.diff_process_groups(new)
-        self.assertEqual([added, removed], [[], []])
-        self.assertEqual(changed, [group1])
-
     def test_readFile_failed(self):
         from supervisor.options import readFile
         try:
Index: src/supervisor/tests/base.py
===================================================================
--- src/supervisor/tests/base.py	(revision 812)
+++ src/supervisor/tests/base.py	(working copy)
@@ -79,6 +79,9 @@
         self.realizeargs = args
         self.realizekw = kw
 
+    def process_config_file(self):
+        pass
+
     def cleanup_fds(self):
         self.fds_cleaned_up = True
 
@@ -767,7 +770,7 @@
         raise Fault(xmlrpc.Faults.SHUTDOWN_STATE, '')
 
     def reloadConfig(self):
-        return [[['added'], ['changed'], ['dropped']]]
+        return [[['added'], ['changed'], ['removed']]]
 
     def addProcessGroup(self, name):
         from xmlrpclib import Fault
Index: src/supervisor/tests/test_supervisorctl.py
===================================================================
--- src/supervisor/tests/test_supervisorctl.py	(revision 812)
+++ src/supervisor/tests/test_supervisorctl.py	(working copy)
@@ -472,6 +472,54 @@
         self.assertEqual(result, None)
         self.assertEqual(options._server.supervisor._shutdown, True)
 
+    def test__formatChanges(self):
+        plugin = self._makeOne()
+        # Don't explode, plz
+        plugin._formatChanges([['added'], ['changed'], ['removed']])
+        plugin._formatChanges([[], [], []])
+
+    def test_reread(self):
+        plugin = self._makeOne()
+        calls = []
+        plugin._formatChanges = lambda x: calls.append(x)
+        result = plugin.do_reread(None)
+        self.assertEqual(result, None)
+        self.assertEqual(calls[0], [['added'], ['changed'], ['removed']])
+
+    def test__formatConfigInfo(self):
+        info = { 'group': 'group1',
+                 'name': 'process1',
+                 'inuse': True,
+                 'autostart': True,
+                 'process_prio': 999,
+                 'group_prio': 999 }
+        plugin = self._makeOne()
+        result = plugin._formatConfigInfo(info)
+        self.assertTrue('in use' in result)
+        info = { 'group': 'group1',
+                 'name': 'process1',
+                 'inuse': False,
+                 'autostart': False,
+                 'process_prio': 999,
+                 'group_prio': 999 }
+        result = plugin._formatConfigInfo(info)
+        self.assertTrue('avail' in result)
+
+    def test_avail(self):
+        calls = []
+        plugin = self._makeOne()
+
+        class FakeSupervisor(object):
+            def getAllConfigInfo(self):
+                return [{ 'group': 'group1', 'name': 'process1',
+                          'inuse': False, 'autostart': False,
+                          'process_prio': 999, 'group_prio': 999 }]
+
+        plugin.ctl.get_supervisor = lambda : FakeSupervisor()
+        plugin.ctl.output = calls.append
+        result = plugin.do_avail('')
+        self.assertEqual(result, None)
+
     def test_add(self):
         plugin = self._makeOne()
         result = plugin.do_add('foo')
@@ -521,6 +569,126 @@
         self.assertEqual(plugin.ctl.stdout.getvalue(),
                          'ERROR: process/group still running: STILL_RUNNING\n')
 
+    def test_update_not_on_shutdown(self):
+        plugin = self._makeOne()
+        supervisor = plugin.ctl.options._server.supervisor
+        def reloadConfig():
+            from supervisor import xmlrpc
+            import xmlrpclib
+            raise xmlrpclib.Fault(xmlrpc.Faults.SHUTDOWN_STATE, 'blah')
+        supervisor.reloadConfig = reloadConfig
+        supervisor.processes = ['removed']
+        plugin.do_update('')
+        self.assertEqual(supervisor.processes, ['removed'])
+
+    def test_update_added_procs(self):
+        plugin = self._makeOne()
+        supervisor = plugin.ctl.options._server.supervisor
+
+        calls = []
+        def reloadConfig():
+            return [[['new_proc'], [], []]]
+        supervisor.reloadConfig = reloadConfig
+
+        plugin.do_update('')
+        self.assertEqual(supervisor.processes, ['new_proc'])
+
+    def test_update_changed_procs(self):
+        from supervisor import xmlrpc
+        import xmlrpclib
+
+        plugin = self._makeOne()
+        supervisor = plugin.ctl.options._server.supervisor
+
+        calls = []
+        def reloadConfig():
+            return [[[], ['changed_group'], []]]
+        supervisor.reloadConfig = reloadConfig
+        supervisor.startProcess = lambda x: calls.append(('start', x))
+
+        supervisor.addProcessGroup('changed_group') # fake existence
+        results = [{'name':        'changed_process',
+                    'group':       'changed_group',
+                    'status':      xmlrpc.Faults.SUCCESS,
+                    'description': 'blah'}]
+        def stopProcessGroup(name):
+            calls.append(('stop', name))
+            return results
+        supervisor.stopProcessGroup = stopProcessGroup
+
+        plugin.do_update('')
+        self.assertEqual(calls, [('stop', 'changed_group')])
+
+        supervisor.addProcessGroup('changed_group') # fake existence
+        calls[:] = []
+        results[:] = [{'name':        'changed_process1',
+                       'group':       'changed_group',
+                       'status':      xmlrpc.Faults.NOT_RUNNING,
+                       'description': 'blah'},
+                      {'name':        'changed_process2',
+                       'group':       'changed_group',
+                       'status':      xmlrpc.Faults.FAILED,
+                       'description': 'blah'}]
+
+        plugin.do_update('')
+        self.assertEqual(calls, [('stop', 'changed_group')])
+
+        supervisor.addProcessGroup('changed_group') # fake existence
+        calls[:] = []
+        results[:] = [{'name':        'changed_process1',
+                       'group':       'changed_group',
+                       'status':      xmlrpc.Faults.FAILED,
+                       'description': 'blah'},
+                      {'name':        'changed_process2',
+                       'group':       'changed_group',
+                       'status':      xmlrpc.Faults.SUCCESS,
+                       'description': 'blah'}]
+
+        plugin.do_update('')
+        self.assertEqual(calls, [('stop', 'changed_group')])
+
+    def test_update_removed_procs(self):
+        from supervisor import xmlrpc
+        import xmlrpclib
+
+        plugin = self._makeOne()
+        supervisor = plugin.ctl.options._server.supervisor
+
+        def reloadConfig():
+            return [[[], [], ['removed_group']]]
+        supervisor.reloadConfig = reloadConfig
+
+        results = [{'name':        'removed_process',
+                    'group':       'removed_group',
+                    'status':      xmlrpc.Faults.SUCCESS,
+                    'description': 'blah'}]
+        supervisor.processes = ['removed_group']
+
+        def stopProcessGroup(name):
+            return results
+        supervisor.stopProcessGroup = stopProcessGroup
+
+        plugin.do_update('')
+        self.assertEqual(supervisor.processes, [])
+
+        results[:] = [{'name':        'removed_process',
+                       'group':       'removed_group',
+                       'status':      xmlrpc.Faults.NOT_RUNNING,
+                       'description': 'blah'}]
+        supervisor.processes = ['removed_group']
+
+        plugin.do_update('')
+        self.assertEqual(supervisor.processes, [])
+
+        results[:] = [{'name':        'removed_process',
+                       'group':       'removed_group',
+                       'status':      xmlrpc.Faults.FAILED,
+                       'description': 'blah'}]
+        supervisor.processes = ['removed_group']
+
+        plugin.do_update('')
+        self.assertEqual(supervisor.processes, ['removed_group'])
+
     def test_pid(self):
         plugin = self._makeOne()
         result = plugin.do_pid('')
Index: src/supervisor/tests/test_supervisord.py
===================================================================
--- src/supervisor/tests/test_supervisord.py	(revision 812)
+++ src/supervisor/tests/test_supervisord.py	(working copy)
@@ -173,6 +173,106 @@
         self.assertEqual(options.logger.data[0],
                          'received SIGUSR1 indicating nothing')
 
+    def test_diff_add_remove(self):
+        options = DummyOptions()
+        supervisord = self._makeOne(options)
+
+        pconfig = DummyPConfig(options, 'process1', 'process1')
+        group1 = DummyPGroupConfig(options, 'group1', pconfigs=[pconfig])
+
+        pconfig = DummyPConfig(options, 'process2', 'process2')
+        group2 = DummyPGroupConfig(options, 'group2', pconfigs=[pconfig])
+
+        new = [group1, group2]
+
+        added, changed, removed = supervisord.diff_to_active()
+        self.assertEqual(added, [])
+        self.assertEqual(changed, [])
+        self.assertEqual(removed, [])
+
+        added, changed, removed = supervisord.diff_to_active(new)
+        self.assertEqual(added, new)
+        self.assertEqual(changed, [])
+        self.assertEqual(removed, [])
+
+        supervisord.options.process_group_configs = new
+        added, changed, removed = supervisord.diff_to_active()
+        self.assertEqual(added, new)
+
+        supervisord.add_process_group(group1)
+        supervisord.add_process_group(group2)
+
+        pconfig = DummyPConfig(options, 'process3', 'process3')
+        new_group1 = DummyPGroupConfig(options, pconfigs=[pconfig])
+
+        pconfig = DummyPConfig(options, 'process4', 'process4')
+        new_group2 = DummyPGroupConfig(options, pconfigs=[pconfig])
+
+        new = [group2, new_group1, new_group2]
+
+        added, changed, removed = supervisord.diff_to_active(new)
+        self.assertEqual(added, [new_group1, new_group2])
+        self.assertEqual(changed, [])
+        self.assertEqual(removed, [group1])
+
+    def test_diff_changed(self):
+        from supervisor.options import ProcessConfig, ProcessGroupConfig
+
+        options = DummyOptions()
+        supervisord = self._makeOne(options)
+
+        def make_pconfig(name, command, **params):
+            result = {
+                'name': name, 'command': command,
+                'directory': None, 'umask': None, 'priority': 999, 'autostart': True,
+                'autorestart': True, 'startsecs': 10, 'startretries': 999,
+                'uid': None, 'stdout_logfile': None, 'stdout_capture_maxbytes': 0,
+                'stdout_logfile_backups': 0, 'stdout_logfile_maxbytes': 0,
+                'stderr_logfile': None, 'stderr_capture_maxbytes': 0,
+                'stderr_logfile_backups': 0, 'stderr_logfile_maxbytes': 0,
+                'redirect_stderr': False,
+                'stopsignal': None, 'stopwaitsecs': 10,
+                'exitcodes': (0,2), 'environment': None, 'serverurl': None }
+            result.update(params)
+            return ProcessConfig(options, **result)
+
+        def make_gconfig(name, pconfigs):
+            return ProcessGroupConfig(options, name, 25, pconfigs)
+
+        pconfig = make_pconfig('process1', 'process1', uid='new')
+        group1 = make_gconfig('group1', [pconfig])
+
+        pconfig = make_pconfig('process2', 'process2')
+        group2 = make_gconfig('group2', [pconfig])
+        new = [group1, group2]
+
+        pconfig = make_pconfig('process1', 'process1', uid='old')
+        group3 = make_gconfig('group1', [pconfig])
+
+        pconfig = make_pconfig('process2', 'process2')
+        group4 = make_gconfig('group2', [pconfig])
+        supervisord.add_process_group(group3)
+        supervisord.add_process_group(group4)
+
+        added, changed, removed = supervisord.diff_to_active(new)
+
+        self.assertEqual([added, removed], [[], []])
+        self.assertEqual(changed, [group1])
+
+        options = DummyOptions()
+        supervisord = self._makeOne(options)
+
+        pconfig1 = make_pconfig('process1', 'process1')
+        pconfig2 = make_pconfig('process2', 'process2')
+        group1 = make_gconfig('group1', [pconfig1, pconfig2])
+        new = [group1]
+
+        supervisord.add_process_group(make_gconfig('group1', [pconfig1]))
+
+        added, changed, removed = supervisord.diff_to_active(new)
+        self.assertEqual([added, removed], [[], []])
+        self.assertEqual(changed, [group1])
+
     def test_add_process_group(self):
         options = DummyOptions()
         pconfig = DummyPConfig(options, 'foo', 'foo', '/bin/foo')
Index: src/supervisor/tests/test_rpcinterfaces.py
===================================================================
--- src/supervisor/tests/test_rpcinterfaces.py	(revision 812)
+++ src/supervisor/tests/test_rpcinterfaces.py	(working copy)
@@ -218,6 +218,20 @@
         self.assertEqual(value, True)
         self.assertEqual(supervisord.options.mood, 0)
 
+    def test_reloadConfig(self):
+        options = DummyOptions()
+        supervisord = DummySupervisor(options)
+        interface = self._makeOne(supervisord)
+
+        changes = [ [DummyPGroupConfig(options, 'added')],
+                    [DummyPGroupConfig(options, 'changed')],
+                    [DummyPGroupConfig(options, 'dropped')] ]
+
+        supervisord.diff_to_active = lambda : changes
+
+        value = interface.reloadConfig()
+        self.assertEqual(value, [[['added'], ['changed'], ['dropped']]])
+
     def test_addProcessGroup(self):
         from supervisor.supervisord import Supervisor
         from supervisor import xmlrpc
@@ -821,6 +835,31 @@
         self.assertEqual(result[1]['status'],  Faults.SUCCESS)
         self.assertEqual(result[1]['description'], 'OK')
 
+    def test_getAllConfigInfo(self):
+        options = DummyOptions()
+        supervisord = DummySupervisor(options, 'foo')
+
+        pconfig1 = DummyPConfig(options, 'process1', __file__)
+        pconfig2 = DummyPConfig(options, 'process2', __file__)
+        gconfig = DummyPGroupConfig(options, 'group1', pconfigs=[pconfig1, pconfig2])
+        supervisord.process_groups = {'group1': DummyProcessGroup(gconfig)}
+        supervisord.options.process_group_configs = [gconfig]
+
+        interface = self._makeOne(supervisord)
+        configs = interface.getAllConfigInfo()
+        self.assertEqual(configs, [{ 'group': 'group1',
+                                     'name': 'process1',
+                                     'inuse': True,
+                                     'autostart': True,
+                                     'process_prio': 999,
+                                     'group_prio': 999 },
+                                   { 'group': 'group1',
+                                     'name': 'process2',
+                                     'inuse': True,
+                                     'autostart': True,
+                                     'process_prio': 999,
+                                     'group_prio': 999 }])
+
     def test__interpretProcessInfo(self):
         supervisord = DummySupervisor()
         interface = self._makeOne(supervisord)
Index: src/supervisor/supervisorctl.py
===================================================================
--- src/supervisor/supervisorctl.py	(revision 812)
+++ src/supervisor/supervisorctl.py	(working copy)
@@ -778,6 +778,66 @@
     def help_reload(self):
         self.ctl.output("reload \t\tRestart the remote supervisord.")
 
+    def _formatChanges(self, (added, changed, dropped)):
+        changedict = {}
+        for n, t in [(added, 'available'),
+                     (changed, 'changed'),
+                     (dropped, 'disappeared')]:
+            changedict.update(dict(zip(n, [t] * len(n))))
+
+        if changedict:
+            for name in sorted(changedict):
+                self.ctl.output("%s: %s" % (name, changedict[name]))
+        else:
+            self.ctl.output("No config updates to proccesses")
+
+    def _formatConfigInfo(self, configinfo):
+        if configinfo['group'] == configinfo['name']:
+            name = configinfo['group']
+        else:
+            name = "%s:%s" % (configinfo['group'], configinfo['name'])
+        formatted = { 'name': name }
+        if configinfo['inuse']:
+            formatted['inuse'] = 'in use'
+        else:
+            formatted['inuse'] = 'avail'
+        if configinfo['autostart']:
+            formatted['autostart'] = 'auto'
+        else:
+            formatted['autostart'] = 'manual'
+        formatted['priority'] = "%s:%s" % (configinfo['group_prio'],
+                                           configinfo['process_prio'])
+
+        template = '%(name)-32s %(inuse)-9s %(autostart)-9s %(priority)s'
+        return template % formatted
+
+    def do_avail(self, arg):
+        supervisor = self.ctl.get_supervisor()
+        try:
+            configinfo = supervisor.getAllConfigInfo()
+        except xmlrpclib.Fault, e:
+            if e.faultCode == xmlrpc.Faults.SHUTDOWN_STATE:
+                self.ctl.output('ERROR: supervisor shutting down')
+        else:
+            for pinfo in configinfo:
+                self.ctl.output(self._formatConfigInfo(pinfo))
+
+    def help_avail(self):
+        self.ctl.output("avail\t\t\tDisplay all configured processes")
+
+    def do_reread(self, arg):
+        supervisor = self.ctl.get_supervisor()
+        try:
+            result = supervisor.reloadConfig()
+        except xmlrpclib.Fault, e:
+            if e.faultCode == xmlrpc.Faults.SHUTDOWN_STATE:
+                self.ctl.output('ERROR: already shutting down')
+        else:
+            self._formatChanges(result[0])
+
+    def help_reread(self):
+        self.ctl.output("reread \t\t\tReload the daemon's configuration files")
+
     def do_add(self, arg):
         names = arg.strip().split()
 
@@ -819,12 +879,55 @@
                 else:
                     raise
             else:
-                self.ctl.output("%s: removed" % name)
+                self.ctl.output("%s: removed process group" % name)
 
     def help_remove(self):
         self.ctl.output("remove <name> [...]\tRemoves process/group from "
                         "active config")
 
+    def do_update(self, arg):
+        def log(name, message):
+            self.ctl.output("%s: %s" % (name, message))
+
+        supervisor = self.ctl.get_supervisor()
+        try:
+            result = supervisor.reloadConfig()
+        except xmlrpclib.Fault, e:
+            if e.faultCode == xmlrpc.Faults.SHUTDOWN_STATE:
+                self.ctl.output('ERROR: already shutting down')
+                return
+            else:
+                raise e
+
+        added, changed, removed = result[0]
+
+        for gname in removed:
+            results = supervisor.stopProcessGroup(gname)
+            log(gname, "stopped")
+
+            fails = [res for res in results
+                     if res['status'] == xmlrpc.Faults.FAILED]
+            if fails:
+                log(gname, "has problems; not removing")
+                continue
+            supervisor.removeProcessGroup(gname)
+            log(gname, "removed process group")
+
+        for gname in changed:
+            results = supervisor.stopProcessGroup(gname)
+            log(gname, "stopped")
+
+            supervisor.removeProcessGroup(gname)
+            supervisor.addProcessGroup(gname)
+            log(gname, "updated process group")
+
+        for gname in added:
+            supervisor.addProcessGroup(gname)
+            log(gname, "added process group")
+
+    def help_update(self):
+        self.ctl.output("update\t\tReload config and add/remove as necessary")
+
     def _clearresult(self, result):
         name = result['name']
         code = result['status']
Index: src/supervisor/supervisord.py
===================================================================
--- src/supervisor/supervisord.py	(revision 812)
+++ src/supervisor/supervisord.py	(working copy)
@@ -108,6 +108,22 @@
         finally:
             self.options.cleanup()
 
+    def diff_to_active(self, new=None):
+        if not new:
+            new = self.options.process_group_configs
+        cur = [group.config for group in self.process_groups.values()]
+
+        curdict = dict(zip([cfg.name for cfg in cur], cur))
+        newdict = dict(zip([cfg.name for cfg in new], new))
+
+        added   = [cand for cand in new if cand.name not in curdict]
+        removed = [cand for cand in cur if cand.name not in newdict]
+
+        changed = [cand for cand in new
+                   if cand != curdict.get(cand.name, cand)]
+
+        return added, changed, removed
+
     def add_process_group(self, config):
         name = config.name
         if name not in self.process_groups:

