mirror of
https://github.com/textmate/textmate.git
synced 2026-04-28 03:00:34 -04:00
Remove old SCM API
This commit is contained in:
@@ -1,205 +1,385 @@
|
||||
#include "drivers/api.h"
|
||||
#include "scm.h"
|
||||
#include "drivers/api.h"
|
||||
#include "snapshot.h"
|
||||
#include "fs_events.h"
|
||||
#include <io/path.h>
|
||||
#include <cf/cf.h>
|
||||
#include <oak/oak.h>
|
||||
#include <text/format.h>
|
||||
#include <settings/settings.h>
|
||||
|
||||
OAK_DEBUG_VAR(SCM);
|
||||
|
||||
namespace scm
|
||||
{
|
||||
static std::map<std::string, info_ptr>& cache () { static std::map<std::string, info_ptr> cache; return cache; }
|
||||
driver_t* git_driver ();
|
||||
driver_t* hg_driver ();
|
||||
driver_t* p4_driver ();
|
||||
driver_t* svn_driver ();
|
||||
|
||||
} /* scm */
|
||||
|
||||
namespace scm { namespace ng
|
||||
{
|
||||
static bool Disabled = false;
|
||||
static std::map<std::string, shared_info_weak_ptr> PendingUpdates;
|
||||
|
||||
// =================
|
||||
// = shared_info_t =
|
||||
// =================
|
||||
|
||||
struct shared_info_t : std::enable_shared_from_this<shared_info_t>
|
||||
{
|
||||
shared_info_t (std::string const& rootPath, scm::driver_t const* driver);
|
||||
~shared_info_t ();
|
||||
|
||||
std::string const& root_path () const { return _root_path; }
|
||||
std::map<std::string, std::string> const& variables () const { return _variables; }
|
||||
std::map<std::string, scm::status::type> const& status () const { return _status; }
|
||||
bool tracks_directories () const { return _driver->tracks_directories(); }
|
||||
|
||||
void add_client (info_t* client);
|
||||
void remove_client (info_t* client);
|
||||
|
||||
private:
|
||||
friend void enable ();
|
||||
|
||||
void schedule_update ();
|
||||
static void async_update (shared_info_weak_ptr weakThis);
|
||||
void fs_did_change (std::set<std::string> const& changedPaths);
|
||||
|
||||
void update (std::map<std::string, std::string> const& variables, std::map<std::string, scm::status::type> const& status, fs::snapshot_t const& fsSnapshot);
|
||||
|
||||
std::string _root_path;
|
||||
scm::driver_t const* _driver;
|
||||
|
||||
std::map<std::string, std::string> _variables;
|
||||
std::map<std::string, scm::status::type> _status;
|
||||
fs::snapshot_t _fs_snapshot;
|
||||
|
||||
bool _pending_update = false;
|
||||
std::chrono::steady_clock::time_point _no_check_before = std::chrono::steady_clock::now();
|
||||
std::shared_ptr<watcher_t> _watcher;
|
||||
std::set<info_t*> _clients;
|
||||
};
|
||||
|
||||
// ==========
|
||||
// = info_t =
|
||||
// ==========
|
||||
|
||||
info_t::info_t (std::string const& wcPath, driver_t const* driver) : _wc_path(wcPath), _driver(driver)
|
||||
info_t::info_t (std::string const& path)
|
||||
{
|
||||
ASSERTF(path::is_directory(_wc_path) || !path::exists(_wc_path) || _wc_path == NULL_STR, "Path: %s\n", _wc_path.c_str());
|
||||
}
|
||||
|
||||
std::string info_t::scm_name () const { auto vars = variables(); auto iter = vars.find("TM_SCM_NAME"); return iter != vars.end() ? iter->second : NULL_STR; }
|
||||
std::string info_t::path () const { return _wc_path; }
|
||||
std::string info_t::branch () const { auto vars = variables(); auto iter = vars.find("TM_SCM_BRANCH"); return iter != vars.end() ? iter->second : NULL_STR; }
|
||||
bool info_t::tracks_directories () const { return _driver->tracks_directories(); }
|
||||
|
||||
void info_t::setup ()
|
||||
info_t::~info_t ()
|
||||
{
|
||||
if(_did_setup)
|
||||
return;
|
||||
if(_shared_info)
|
||||
_shared_info->remove_client(this);
|
||||
|
||||
_file_status = _driver->status(_wc_path);
|
||||
_variables = std::map<std::string, std::string>{
|
||||
{ "TM_SCM_NAME", _driver->name() },
|
||||
{ "TM_SCM_BRANCH", _driver->branch_name(_wc_path) }
|
||||
};
|
||||
_watcher.reset(new scm::watcher_t(_wc_path, std::bind(&info_t::callback, this, std::placeholders::_1)));
|
||||
_did_setup = true;
|
||||
for(auto block : _callbacks)
|
||||
Block_release(block);
|
||||
}
|
||||
|
||||
status::type info_t::status (std::string const& path)
|
||||
void info_t::set_shared_info (shared_info_ptr sharedInfo)
|
||||
{
|
||||
D(DBF_SCM, bug("%s\n", path.c_str()););
|
||||
setup();
|
||||
status_map_t::const_iterator res = _file_status.find(path);
|
||||
return res != _file_status.end() ? res->second : status::none;
|
||||
if(_shared_info)
|
||||
_shared_info->remove_client(this);
|
||||
if(_shared_info = sharedInfo)
|
||||
_shared_info->add_client(this);
|
||||
}
|
||||
|
||||
bool info_t::dry () const
|
||||
{
|
||||
return !_shared_info;
|
||||
}
|
||||
|
||||
std::string info_t::root_path () const
|
||||
{
|
||||
return dry() ? NULL_STR : _shared_info->root_path();
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> info_t::variables () const
|
||||
{
|
||||
const_cast<info_t*>(this)->setup();
|
||||
return _variables;
|
||||
return dry() ? std::map<std::string, std::string>() : _shared_info->variables();
|
||||
}
|
||||
|
||||
status_map_t info_t::files_with_status (int mask)
|
||||
std::map<std::string, scm::status::type> const& info_t::status () const
|
||||
{
|
||||
setup();
|
||||
static std::map<std::string, scm::status::type> const EmptyMap;
|
||||
return dry() ? EmptyMap : _shared_info->status();
|
||||
}
|
||||
|
||||
status_map_t res;
|
||||
iterate(status, _file_status)
|
||||
scm::status::type info_t::status (std::string const& path) const
|
||||
{
|
||||
auto const& map = status();
|
||||
auto it = map.find(path);
|
||||
return dry() || path == NULL_STR ? scm::status::unknown : (it != map.end() ? it->second : scm::status::none);
|
||||
}
|
||||
|
||||
bool info_t::tracks_directories () const
|
||||
{
|
||||
return dry() ? false : _shared_info->tracks_directories();
|
||||
}
|
||||
|
||||
void info_t::add_callback (void (^block)(info_t const&))
|
||||
{
|
||||
_callbacks.push_back(Block_copy(block));
|
||||
if(!dry())
|
||||
did_update_shared_info();
|
||||
}
|
||||
|
||||
void info_t::did_update_shared_info ()
|
||||
{
|
||||
for(auto callback : _callbacks)
|
||||
callback(*this);
|
||||
}
|
||||
|
||||
// =================
|
||||
// = shared_info_t =
|
||||
// =================
|
||||
|
||||
shared_info_t::shared_info_t (std::string const& rootPath, scm::driver_t const* driver) : _root_path(rootPath), _driver(driver)
|
||||
{
|
||||
}
|
||||
|
||||
shared_info_t::~shared_info_t ()
|
||||
{
|
||||
}
|
||||
|
||||
void shared_info_t::add_client (info_t* client)
|
||||
{
|
||||
_clients.insert(client);
|
||||
if(_clients.size() == 1)
|
||||
{
|
||||
if((status->second & mask) == status->second)
|
||||
res.insert(*status);
|
||||
_watcher.reset(new scm::watcher_t(_root_path, std::bind(&shared_info_t::fs_did_change, this, std::placeholders::_1)));
|
||||
schedule_update();
|
||||
}
|
||||
else
|
||||
{
|
||||
client->did_update_shared_info();
|
||||
}
|
||||
}
|
||||
|
||||
void shared_info_t::remove_client (info_t* client)
|
||||
{
|
||||
if(_clients.size() == 1)
|
||||
_watcher.reset();
|
||||
_clients.erase(client);
|
||||
}
|
||||
|
||||
void shared_info_t::update (std::map<std::string, std::string> const& variables, std::map<std::string, scm::status::type> const& status, fs::snapshot_t const& fsSnapshot)
|
||||
{
|
||||
bool shouldNotify = _variables != variables || _status != status;
|
||||
|
||||
_variables = variables;
|
||||
_status = status;
|
||||
_fs_snapshot = fsSnapshot;
|
||||
|
||||
if(shouldNotify)
|
||||
{
|
||||
for(info_t* client : _clients)
|
||||
client->did_update_shared_info();
|
||||
}
|
||||
}
|
||||
|
||||
// =================
|
||||
// = Update Status =
|
||||
// =================
|
||||
|
||||
void shared_info_t::async_update (shared_info_weak_ptr weakThis)
|
||||
{
|
||||
if(shared_info_ptr info = weakThis.lock())
|
||||
{
|
||||
if(!info->_driver->may_touch_filesystem() || info->_fs_snapshot != fs::snapshot_t(info->_root_path))
|
||||
{
|
||||
std::map<std::string, std::string> variables{ { "TM_SCM_NAME", info->_driver->name() } };
|
||||
std::string const branch = info->_driver->branch_name(info->_root_path);
|
||||
if(branch != NULL_STR)
|
||||
variables.insert(std::make_pair("TM_SCM_BRANCH", branch));
|
||||
scm::status_map_t const status = info->_driver->status(info->_root_path);
|
||||
fs::snapshot_t const snapshot = info->_driver->may_touch_filesystem() ? fs::snapshot_t(info->_root_path) : fs::snapshot_t();
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
info->update(variables, status, snapshot);
|
||||
});
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
info->_pending_update = false;
|
||||
info->_no_check_before = std::chrono::steady_clock::now() + std::chrono::seconds(3);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void shared_info_t::schedule_update ()
|
||||
{
|
||||
if(Disabled)
|
||||
{
|
||||
PendingUpdates[_root_path] = shared_from_this();
|
||||
return;
|
||||
}
|
||||
|
||||
if(_pending_update)
|
||||
return;
|
||||
_pending_update = true;
|
||||
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
dispatch_time_t delay = now < _no_check_before ? dispatch_time(DISPATCH_TIME_NOW, std::chrono::duration_cast<std::chrono::duration<int64_t, std::nano>>(_no_check_before - now).count()) : DISPATCH_TIME_NOW;
|
||||
|
||||
shared_info_weak_ptr weakThis = shared_from_this();
|
||||
static dispatch_queue_t queue = dispatch_queue_create("org.textmate.scm.status", DISPATCH_QUEUE_SERIAL);
|
||||
dispatch_after(delay, queue, ^{
|
||||
async_update(weakThis);
|
||||
});
|
||||
}
|
||||
|
||||
void shared_info_t::fs_did_change (std::set<std::string> const& changedPaths)
|
||||
{
|
||||
schedule_update();
|
||||
}
|
||||
|
||||
// =========
|
||||
// = Other =
|
||||
// =========
|
||||
|
||||
static std::map<std::string, shared_info_weak_ptr>& cache ()
|
||||
{
|
||||
static auto res = new std::map<std::string, shared_info_weak_ptr>;
|
||||
return *res;
|
||||
}
|
||||
|
||||
static dispatch_queue_t cache_access_queue ()
|
||||
{
|
||||
static dispatch_queue_t res = dispatch_queue_create("org.textmate.scm.info-cache", DISPATCH_QUEUE_SERIAL);
|
||||
return res;
|
||||
}
|
||||
|
||||
// ====================
|
||||
// = Callback Related =
|
||||
// ====================
|
||||
|
||||
void info_t::add_callback (callback_t* cb)
|
||||
static std::vector<driver_t*> const& drivers ()
|
||||
{
|
||||
D(DBF_SCM, bug("%s / %p\n", path().c_str(), cb););
|
||||
_callbacks.add(cb);
|
||||
cb->status_changed(*this, std::set<std::string>());
|
||||
static auto const res = new std::vector<driver_t*>{ scm::git_driver(), scm::hg_driver(), scm::p4_driver(), scm::svn_driver() };
|
||||
return *res;
|
||||
}
|
||||
|
||||
void info_t::remove_callback (callback_t* cb)
|
||||
static shared_info_ptr find_shared_info_for (std::string const& path)
|
||||
{
|
||||
_callbacks.remove(cb);
|
||||
}
|
||||
|
||||
static bool test_and_set (std::string const& key, bool flag)
|
||||
{
|
||||
static dispatch_queue_t queue = dispatch_queue_create("org.textmate.scm.coordination", DISPATCH_QUEUE_SERIAL);
|
||||
static std::set<std::string> keys;
|
||||
|
||||
__block bool foundKey = false;
|
||||
dispatch_sync(queue, ^{
|
||||
auto it = keys.find(key);
|
||||
foundKey = it != keys.end();
|
||||
if(flag && !foundKey)
|
||||
keys.insert(key);
|
||||
else if(!flag && foundKey)
|
||||
keys.erase(it);
|
||||
});
|
||||
return foundKey;
|
||||
}
|
||||
|
||||
void info_t::callback (std::set<std::string> const& pathsChangedOnDisk)
|
||||
{
|
||||
D(DBF_SCM, bug("( %s )\n", text::join(pathsChangedOnDisk, ", ").c_str()););
|
||||
if(test_and_set(_wc_path, true))
|
||||
return;
|
||||
|
||||
static dispatch_queue_t queue = dispatch_queue_create("org.textmate.scm.status", DISPATCH_QUEUE_SERIAL);
|
||||
dispatch_async(queue, ^{
|
||||
double elapsed = oak::date_t::now() - _updated;
|
||||
if(elapsed < 3)
|
||||
usleep((3 - elapsed) * 1000000);
|
||||
|
||||
bool shouldCheck = !_driver->may_touch_filesystem() || _snapshot != fs::snapshot_t(_wc_path);
|
||||
test_and_set(_wc_path, false);
|
||||
if(shouldCheck)
|
||||
{
|
||||
scm::status_map_t const status = _driver->status(_wc_path);
|
||||
std::map<std::string, std::string> const variables{
|
||||
{ "TM_SCM_NAME", _driver->name() },
|
||||
{ "TM_SCM_BRANCH", _driver->branch_name(_wc_path) }
|
||||
};
|
||||
fs::snapshot_t const snapshot = _driver->may_touch_filesystem() ? fs::snapshot_t(_wc_path) : fs::snapshot_t();
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
update_status(_wc_path, snapshot, status, variables);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void info_t::update_status (std::string const& path, fs::snapshot_t const& snapshot, scm::status_map_t const& newStatus, std::map<std::string, std::string> const& newVariables)
|
||||
{
|
||||
auto it = cache().find(path);
|
||||
if(it != cache().end())
|
||||
for(std::string cwd = path; cwd != "/"; cwd = path::parent(cwd))
|
||||
{
|
||||
std::set<std::string> changedPaths;
|
||||
|
||||
auto const oldStatus = it->second->_file_status;
|
||||
auto oldStatusIter = oldStatus.begin();
|
||||
auto newStatusIter = newStatus.begin();
|
||||
|
||||
while(oldStatusIter != oldStatus.end() || newStatusIter != newStatus.end())
|
||||
auto it = cache().find(cwd);
|
||||
if(it != cache().end())
|
||||
{
|
||||
if(newStatusIter == newStatus.end() || oldStatusIter != oldStatus.end() && oldStatusIter->first < newStatusIter->first)
|
||||
{
|
||||
changedPaths.insert(oldStatusIter->first);
|
||||
++oldStatusIter;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(oldStatusIter == oldStatus.end() || newStatusIter != newStatus.end() && newStatusIter->first < oldStatusIter->first)
|
||||
{
|
||||
changedPaths.insert(newStatusIter->first);
|
||||
++newStatusIter;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(oldStatusIter->second != newStatusIter->second)
|
||||
changedPaths.insert(newStatusIter->first);
|
||||
|
||||
++oldStatusIter;
|
||||
++newStatusIter;
|
||||
if(shared_info_ptr res = it->second.lock())
|
||||
return res;
|
||||
}
|
||||
|
||||
it->second->_updated = oak::date_t::now();
|
||||
it->second->_snapshot = snapshot;
|
||||
it->second->_file_status = newStatus;
|
||||
it->second->_variables = newVariables;
|
||||
|
||||
it->second->_callbacks(&callback_t::status_changed, *it->second, changedPaths);
|
||||
for(driver_t* driver : drivers())
|
||||
{
|
||||
if(driver && driver->has_info_for_directory(cwd))
|
||||
{
|
||||
shared_info_ptr res(new shared_info_t(cwd, driver));
|
||||
cache()[cwd] = res;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
return shared_info_ptr();
|
||||
}
|
||||
|
||||
// ==============
|
||||
// = Public API =
|
||||
// ==============
|
||||
|
||||
info_ptr info (std::string const& dir)
|
||||
static bool scm_enabled_for_path (std::string const& path)
|
||||
{
|
||||
if(!settings_for_path(NULL_STR, scope::scope_t(), dir).get(kSettingsSCMStatusKey, true))
|
||||
return path != "" && path != "/" && path[0] == '/' && path != path::home() && path::is_local(path) && settings_for_path(NULL_STR, "", path).get(kSettingsSCMStatusKey, true);
|
||||
}
|
||||
|
||||
void disable ()
|
||||
{
|
||||
Disabled = true;
|
||||
}
|
||||
|
||||
void enable ()
|
||||
{
|
||||
Disabled = false;
|
||||
for(auto pair : PendingUpdates)
|
||||
{
|
||||
if(shared_info_ptr info = pair.second.lock())
|
||||
info->schedule_update();
|
||||
}
|
||||
PendingUpdates.clear();
|
||||
}
|
||||
|
||||
std::string root_for_path (std::string const& path)
|
||||
{
|
||||
if(!scm_enabled_for_path(path))
|
||||
return NULL_STR;
|
||||
|
||||
__block std::string res = NULL_STR;
|
||||
dispatch_sync(cache_access_queue(), ^{
|
||||
for(std::string cwd = path; res == NULL_STR && cwd != "/"; cwd = path::parent(cwd))
|
||||
{
|
||||
auto it = cache().find(cwd);
|
||||
if(it != cache().end())
|
||||
{
|
||||
res = cwd;
|
||||
}
|
||||
else
|
||||
{
|
||||
for(driver_t* driver : drivers())
|
||||
{
|
||||
if(driver && driver->has_info_for_directory(cwd))
|
||||
{
|
||||
res = cwd;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
info_ptr info (std::string path)
|
||||
{
|
||||
if(!scm_enabled_for_path(path))
|
||||
return info_ptr();
|
||||
|
||||
std::string wcPath;
|
||||
if(driver_t const* driver = driver_for_path(dir, &wcPath))
|
||||
info_ptr res(new info_t(path));
|
||||
|
||||
__block bool performBackgroundDriverSearch = true;
|
||||
dispatch_sync(cache_access_queue(), ^{
|
||||
for(std::string cwd = path; cwd != "/"; cwd = path::parent(cwd))
|
||||
{
|
||||
auto it = cache().find(cwd);
|
||||
if(it != cache().end())
|
||||
{
|
||||
if(shared_info_ptr sharedInfo = it->second.lock())
|
||||
{
|
||||
res->set_shared_info(sharedInfo);
|
||||
performBackgroundDriverSearch = cwd != path;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(performBackgroundDriverSearch)
|
||||
{
|
||||
if(wcPath == NULL_STR || wcPath == "/" || wcPath == path::home() || !path::is_local(wcPath))
|
||||
return info_ptr();
|
||||
|
||||
std::map<std::string, info_ptr>::iterator it = cache().find(wcPath);
|
||||
if(it == cache().end())
|
||||
it = cache().insert(std::make_pair(wcPath, info_ptr(new info_t(wcPath, driver)))).first;
|
||||
return it->second;
|
||||
weak_info_ptr weakInfo = res;
|
||||
dispatch_async(cache_access_queue(), ^{
|
||||
if(shared_info_ptr sharedInfo = find_shared_info_for(path))
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if(info_ptr info = weakInfo.lock())
|
||||
info->set_shared_info(sharedInfo);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return info_ptr();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
status_map_t tracked_files (std::string const& dir, int mask)
|
||||
void wait_for_status (info_ptr info)
|
||||
{
|
||||
if(info_ptr const& driver = info(path::join(dir, ".scm-kludge")))
|
||||
return driver->files_with_status(mask);
|
||||
return status_map_t();
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
info->add_callback(^(scm::ng::info_t const& unused){
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
});
|
||||
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
|
||||
// FIXME remove_callback
|
||||
dispatch_release(semaphore);
|
||||
}
|
||||
}
|
||||
|
||||
} /* ng */ } /* scm */
|
||||
|
||||
@@ -1,65 +1,50 @@
|
||||
#ifndef OAKSCM_H_SUWJ53QQ
|
||||
#define OAKSCM_H_SUWJ53QQ
|
||||
#ifndef SCM_NG_H_FXJGXN9B
|
||||
#define SCM_NG_H_FXJGXN9B
|
||||
|
||||
#include "scm_ng.h"
|
||||
#include "status.h"
|
||||
#include "snapshot.h"
|
||||
#include <plist/date.h>
|
||||
#include <oak/debug.h>
|
||||
#include <oak/callbacks.h>
|
||||
#include <oak/duration.h>
|
||||
|
||||
namespace scm
|
||||
namespace scm { namespace ng
|
||||
{
|
||||
struct driver_t;
|
||||
|
||||
struct info_t;
|
||||
typedef std::shared_ptr<info_t> info_ptr;
|
||||
typedef std::weak_ptr<info_t> weak_info_ptr;
|
||||
|
||||
struct PUBLIC callback_t
|
||||
{
|
||||
virtual void status_changed (scm::info_t const& info, std::set<std::string> const& changedPaths) = 0;
|
||||
virtual ~callback_t () { }
|
||||
};
|
||||
|
||||
struct watcher_t;
|
||||
struct shared_info_t;
|
||||
typedef std::shared_ptr<shared_info_t> shared_info_ptr;
|
||||
typedef std::weak_ptr<shared_info_t> shared_info_weak_ptr;
|
||||
|
||||
struct PUBLIC info_t
|
||||
{
|
||||
info_t (std::string const& wcPath, driver_t const* driver);
|
||||
info_t (std::string const& path);
|
||||
~info_t ();
|
||||
|
||||
std::string scm_name () const;
|
||||
std::string path () const;
|
||||
std::string branch () const;
|
||||
bool tracks_directories () const;
|
||||
status::type status (std::string const& path);
|
||||
bool dry () const;
|
||||
|
||||
std::string root_path () const;
|
||||
std::map<std::string, std::string> variables () const;
|
||||
status_map_t files_with_status (int mask);
|
||||
std::map<std::string, scm::status::type> const& status () const;
|
||||
scm::status::type status (std::string const& path) const;
|
||||
|
||||
void add_callback (callback_t* cb);
|
||||
void remove_callback (callback_t* cb);
|
||||
bool tracks_directories () const;
|
||||
void add_callback (void (^block)(info_t const&));
|
||||
|
||||
private:
|
||||
void setup ();
|
||||
bool _did_setup = false;
|
||||
friend info_ptr info (std::string path);
|
||||
void set_shared_info (shared_info_ptr info);
|
||||
|
||||
std::string _wc_path;
|
||||
driver_t const* _driver;
|
||||
status_map_t _file_status;
|
||||
std::map<std::string, std::string> _variables;
|
||||
oak::date_t _updated;
|
||||
fs::snapshot_t _snapshot;
|
||||
friend shared_info_t;
|
||||
void did_update_shared_info ();
|
||||
|
||||
std::shared_ptr<scm::watcher_t> _watcher;
|
||||
void callback (std::set<std::string> const& pathsChangedOnDisk);
|
||||
oak::callbacks_t<callback_t> _callbacks;
|
||||
|
||||
static void update_status (std::string const& path, fs::snapshot_t const& snapshot, scm::status_map_t const& status, std::map<std::string, std::string> const& variables);
|
||||
std::vector<void(^)(info_t const&)> _callbacks;
|
||||
shared_info_ptr _shared_info;
|
||||
};
|
||||
|
||||
PUBLIC info_ptr info (std::string const& dir);
|
||||
PUBLIC status_map_t tracked_files (std::string const& dir, int mask);
|
||||
PUBLIC void disable ();
|
||||
PUBLIC void enable ();
|
||||
PUBLIC std::string root_for_path (std::string const& path);
|
||||
PUBLIC info_ptr info (std::string path);
|
||||
PUBLIC void wait_for_status (info_ptr info);
|
||||
|
||||
} /* scm */
|
||||
} /* ng */ } /* scm */
|
||||
|
||||
#endif /* end of include guard: OAKSCM_H_SUWJ53QQ */
|
||||
#endif /* end of include guard: SCM_NG_H_FXJGXN9B */
|
||||
|
||||
@@ -1,385 +0,0 @@
|
||||
#include "scm.h"
|
||||
#include "drivers/api.h"
|
||||
#include "snapshot.h"
|
||||
#include "fs_events.h"
|
||||
#include <io/path.h>
|
||||
#include <text/format.h>
|
||||
#include <settings/settings.h>
|
||||
|
||||
namespace scm
|
||||
{
|
||||
driver_t* git_driver ();
|
||||
driver_t* hg_driver ();
|
||||
driver_t* p4_driver ();
|
||||
driver_t* svn_driver ();
|
||||
|
||||
} /* scm */
|
||||
|
||||
namespace scm { namespace ng
|
||||
{
|
||||
static bool Disabled = false;
|
||||
static std::map<std::string, shared_info_weak_ptr> PendingUpdates;
|
||||
|
||||
// =================
|
||||
// = shared_info_t =
|
||||
// =================
|
||||
|
||||
struct shared_info_t : std::enable_shared_from_this<shared_info_t>
|
||||
{
|
||||
shared_info_t (std::string const& rootPath, scm::driver_t const* driver);
|
||||
~shared_info_t ();
|
||||
|
||||
std::string const& root_path () const { return _root_path; }
|
||||
std::map<std::string, std::string> const& variables () const { return _variables; }
|
||||
std::map<std::string, scm::status::type> const& status () const { return _status; }
|
||||
bool tracks_directories () const { return _driver->tracks_directories(); }
|
||||
|
||||
void add_client (info_t* client);
|
||||
void remove_client (info_t* client);
|
||||
|
||||
private:
|
||||
friend void enable ();
|
||||
|
||||
void schedule_update ();
|
||||
static void async_update (shared_info_weak_ptr weakThis);
|
||||
void fs_did_change (std::set<std::string> const& changedPaths);
|
||||
|
||||
void update (std::map<std::string, std::string> const& variables, std::map<std::string, scm::status::type> const& status, fs::snapshot_t const& fsSnapshot);
|
||||
|
||||
std::string _root_path;
|
||||
scm::driver_t const* _driver;
|
||||
|
||||
std::map<std::string, std::string> _variables;
|
||||
std::map<std::string, scm::status::type> _status;
|
||||
fs::snapshot_t _fs_snapshot;
|
||||
|
||||
bool _pending_update = false;
|
||||
std::chrono::steady_clock::time_point _no_check_before = std::chrono::steady_clock::now();
|
||||
std::shared_ptr<watcher_t> _watcher;
|
||||
std::set<info_t*> _clients;
|
||||
};
|
||||
|
||||
// ==========
|
||||
// = info_t =
|
||||
// ==========
|
||||
|
||||
info_t::info_t (std::string const& path)
|
||||
{
|
||||
}
|
||||
|
||||
info_t::~info_t ()
|
||||
{
|
||||
if(_shared_info)
|
||||
_shared_info->remove_client(this);
|
||||
|
||||
for(auto block : _callbacks)
|
||||
Block_release(block);
|
||||
}
|
||||
|
||||
void info_t::set_shared_info (shared_info_ptr sharedInfo)
|
||||
{
|
||||
if(_shared_info)
|
||||
_shared_info->remove_client(this);
|
||||
if(_shared_info = sharedInfo)
|
||||
_shared_info->add_client(this);
|
||||
}
|
||||
|
||||
bool info_t::dry () const
|
||||
{
|
||||
return !_shared_info;
|
||||
}
|
||||
|
||||
std::string info_t::root_path () const
|
||||
{
|
||||
return dry() ? NULL_STR : _shared_info->root_path();
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> info_t::variables () const
|
||||
{
|
||||
return dry() ? std::map<std::string, std::string>() : _shared_info->variables();
|
||||
}
|
||||
|
||||
std::map<std::string, scm::status::type> const& info_t::status () const
|
||||
{
|
||||
static std::map<std::string, scm::status::type> const EmptyMap;
|
||||
return dry() ? EmptyMap : _shared_info->status();
|
||||
}
|
||||
|
||||
scm::status::type info_t::status (std::string const& path) const
|
||||
{
|
||||
auto const& map = status();
|
||||
auto it = map.find(path);
|
||||
return dry() || path == NULL_STR ? scm::status::unknown : (it != map.end() ? it->second : scm::status::none);
|
||||
}
|
||||
|
||||
bool info_t::tracks_directories () const
|
||||
{
|
||||
return dry() ? false : _shared_info->tracks_directories();
|
||||
}
|
||||
|
||||
void info_t::add_callback (void (^block)(info_t const&))
|
||||
{
|
||||
_callbacks.push_back(Block_copy(block));
|
||||
if(!dry())
|
||||
did_update_shared_info();
|
||||
}
|
||||
|
||||
void info_t::did_update_shared_info ()
|
||||
{
|
||||
for(auto callback : _callbacks)
|
||||
callback(*this);
|
||||
}
|
||||
|
||||
// =================
|
||||
// = shared_info_t =
|
||||
// =================
|
||||
|
||||
shared_info_t::shared_info_t (std::string const& rootPath, scm::driver_t const* driver) : _root_path(rootPath), _driver(driver)
|
||||
{
|
||||
}
|
||||
|
||||
shared_info_t::~shared_info_t ()
|
||||
{
|
||||
}
|
||||
|
||||
void shared_info_t::add_client (info_t* client)
|
||||
{
|
||||
_clients.insert(client);
|
||||
if(_clients.size() == 1)
|
||||
{
|
||||
_watcher.reset(new scm::watcher_t(_root_path, std::bind(&shared_info_t::fs_did_change, this, std::placeholders::_1)));
|
||||
schedule_update();
|
||||
}
|
||||
else
|
||||
{
|
||||
client->did_update_shared_info();
|
||||
}
|
||||
}
|
||||
|
||||
void shared_info_t::remove_client (info_t* client)
|
||||
{
|
||||
if(_clients.size() == 1)
|
||||
_watcher.reset();
|
||||
_clients.erase(client);
|
||||
}
|
||||
|
||||
void shared_info_t::update (std::map<std::string, std::string> const& variables, std::map<std::string, scm::status::type> const& status, fs::snapshot_t const& fsSnapshot)
|
||||
{
|
||||
bool shouldNotify = _variables != variables || _status != status;
|
||||
|
||||
_variables = variables;
|
||||
_status = status;
|
||||
_fs_snapshot = fsSnapshot;
|
||||
|
||||
if(shouldNotify)
|
||||
{
|
||||
for(info_t* client : _clients)
|
||||
client->did_update_shared_info();
|
||||
}
|
||||
}
|
||||
|
||||
// =================
|
||||
// = Update Status =
|
||||
// =================
|
||||
|
||||
void shared_info_t::async_update (shared_info_weak_ptr weakThis)
|
||||
{
|
||||
if(shared_info_ptr info = weakThis.lock())
|
||||
{
|
||||
if(!info->_driver->may_touch_filesystem() || info->_fs_snapshot != fs::snapshot_t(info->_root_path))
|
||||
{
|
||||
std::map<std::string, std::string> variables{ { "TM_SCM_NAME", info->_driver->name() } };
|
||||
std::string const branch = info->_driver->branch_name(info->_root_path);
|
||||
if(branch != NULL_STR)
|
||||
variables.insert(std::make_pair("TM_SCM_BRANCH", branch));
|
||||
scm::status_map_t const status = info->_driver->status(info->_root_path);
|
||||
fs::snapshot_t const snapshot = info->_driver->may_touch_filesystem() ? fs::snapshot_t(info->_root_path) : fs::snapshot_t();
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
info->update(variables, status, snapshot);
|
||||
});
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
info->_pending_update = false;
|
||||
info->_no_check_before = std::chrono::steady_clock::now() + std::chrono::seconds(3);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void shared_info_t::schedule_update ()
|
||||
{
|
||||
if(Disabled)
|
||||
{
|
||||
PendingUpdates[_root_path] = shared_from_this();
|
||||
return;
|
||||
}
|
||||
|
||||
if(_pending_update)
|
||||
return;
|
||||
_pending_update = true;
|
||||
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
dispatch_time_t delay = now < _no_check_before ? dispatch_time(DISPATCH_TIME_NOW, std::chrono::duration_cast<std::chrono::duration<int64_t, std::nano>>(_no_check_before - now).count()) : DISPATCH_TIME_NOW;
|
||||
|
||||
shared_info_weak_ptr weakThis = shared_from_this();
|
||||
static dispatch_queue_t queue = dispatch_queue_create("org.textmate.scm.status", DISPATCH_QUEUE_SERIAL);
|
||||
dispatch_after(delay, queue, ^{
|
||||
async_update(weakThis);
|
||||
});
|
||||
}
|
||||
|
||||
void shared_info_t::fs_did_change (std::set<std::string> const& changedPaths)
|
||||
{
|
||||
schedule_update();
|
||||
}
|
||||
|
||||
// =========
|
||||
// = Other =
|
||||
// =========
|
||||
|
||||
static std::map<std::string, shared_info_weak_ptr>& cache ()
|
||||
{
|
||||
static auto res = new std::map<std::string, shared_info_weak_ptr>;
|
||||
return *res;
|
||||
}
|
||||
|
||||
static dispatch_queue_t cache_access_queue ()
|
||||
{
|
||||
static dispatch_queue_t res = dispatch_queue_create("org.textmate.scm.info-cache", DISPATCH_QUEUE_SERIAL);
|
||||
return res;
|
||||
}
|
||||
|
||||
static std::vector<driver_t*> const& drivers ()
|
||||
{
|
||||
static auto const res = new std::vector<driver_t*>{ scm::git_driver(), scm::hg_driver(), scm::p4_driver(), scm::svn_driver() };
|
||||
return *res;
|
||||
}
|
||||
|
||||
static shared_info_ptr find_shared_info_for (std::string const& path)
|
||||
{
|
||||
for(std::string cwd = path; cwd != "/"; cwd = path::parent(cwd))
|
||||
{
|
||||
auto it = cache().find(cwd);
|
||||
if(it != cache().end())
|
||||
{
|
||||
if(shared_info_ptr res = it->second.lock())
|
||||
return res;
|
||||
}
|
||||
|
||||
for(driver_t* driver : drivers())
|
||||
{
|
||||
if(driver && driver->has_info_for_directory(cwd))
|
||||
{
|
||||
shared_info_ptr res(new shared_info_t(cwd, driver));
|
||||
cache()[cwd] = res;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
return shared_info_ptr();
|
||||
}
|
||||
|
||||
static bool scm_enabled_for_path (std::string const& path)
|
||||
{
|
||||
return path != "" && path != "/" && path[0] == '/' && path != path::home() && path::is_local(path) && settings_for_path(NULL_STR, "", path).get(kSettingsSCMStatusKey, true);
|
||||
}
|
||||
|
||||
void disable ()
|
||||
{
|
||||
Disabled = true;
|
||||
}
|
||||
|
||||
void enable ()
|
||||
{
|
||||
Disabled = false;
|
||||
for(auto pair : PendingUpdates)
|
||||
{
|
||||
if(shared_info_ptr info = pair.second.lock())
|
||||
info->schedule_update();
|
||||
}
|
||||
PendingUpdates.clear();
|
||||
}
|
||||
|
||||
std::string root_for_path (std::string const& path)
|
||||
{
|
||||
if(!scm_enabled_for_path(path))
|
||||
return NULL_STR;
|
||||
|
||||
__block std::string res = NULL_STR;
|
||||
dispatch_sync(cache_access_queue(), ^{
|
||||
for(std::string cwd = path; res == NULL_STR && cwd != "/"; cwd = path::parent(cwd))
|
||||
{
|
||||
auto it = cache().find(cwd);
|
||||
if(it != cache().end())
|
||||
{
|
||||
res = cwd;
|
||||
}
|
||||
else
|
||||
{
|
||||
for(driver_t* driver : drivers())
|
||||
{
|
||||
if(driver && driver->has_info_for_directory(cwd))
|
||||
{
|
||||
res = cwd;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
info_ptr info (std::string path)
|
||||
{
|
||||
if(!scm_enabled_for_path(path))
|
||||
return info_ptr();
|
||||
|
||||
info_ptr res(new info_t(path));
|
||||
|
||||
__block bool performBackgroundDriverSearch = true;
|
||||
dispatch_sync(cache_access_queue(), ^{
|
||||
for(std::string cwd = path; cwd != "/"; cwd = path::parent(cwd))
|
||||
{
|
||||
auto it = cache().find(cwd);
|
||||
if(it != cache().end())
|
||||
{
|
||||
if(shared_info_ptr sharedInfo = it->second.lock())
|
||||
{
|
||||
res->set_shared_info(sharedInfo);
|
||||
performBackgroundDriverSearch = cwd != path;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(performBackgroundDriverSearch)
|
||||
{
|
||||
weak_info_ptr weakInfo = res;
|
||||
dispatch_async(cache_access_queue(), ^{
|
||||
if(shared_info_ptr sharedInfo = find_shared_info_for(path))
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if(info_ptr info = weakInfo.lock())
|
||||
info->set_shared_info(sharedInfo);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void wait_for_status (info_ptr info)
|
||||
{
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
info->add_callback(^(scm::ng::info_t const& unused){
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
});
|
||||
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
|
||||
// FIXME remove_callback
|
||||
dispatch_release(semaphore);
|
||||
}
|
||||
|
||||
} /* ng */ } /* scm */
|
||||
@@ -1,50 +0,0 @@
|
||||
#ifndef SCM_NG_H_FXJGXN9B
|
||||
#define SCM_NG_H_FXJGXN9B
|
||||
|
||||
#include "status.h"
|
||||
|
||||
namespace scm { namespace ng
|
||||
{
|
||||
struct info_t;
|
||||
typedef std::shared_ptr<info_t> info_ptr;
|
||||
typedef std::weak_ptr<info_t> weak_info_ptr;
|
||||
|
||||
struct shared_info_t;
|
||||
typedef std::shared_ptr<shared_info_t> shared_info_ptr;
|
||||
typedef std::weak_ptr<shared_info_t> shared_info_weak_ptr;
|
||||
|
||||
struct PUBLIC info_t
|
||||
{
|
||||
info_t (std::string const& path);
|
||||
~info_t ();
|
||||
|
||||
bool dry () const;
|
||||
|
||||
std::string root_path () const;
|
||||
std::map<std::string, std::string> variables () const;
|
||||
std::map<std::string, scm::status::type> const& status () const;
|
||||
scm::status::type status (std::string const& path) const;
|
||||
|
||||
bool tracks_directories () const;
|
||||
void add_callback (void (^block)(info_t const&));
|
||||
|
||||
private:
|
||||
friend info_ptr info (std::string path);
|
||||
void set_shared_info (shared_info_ptr info);
|
||||
|
||||
friend shared_info_t;
|
||||
void did_update_shared_info ();
|
||||
|
||||
std::vector<void(^)(info_t const&)> _callbacks;
|
||||
shared_info_ptr _shared_info;
|
||||
};
|
||||
|
||||
PUBLIC void disable ();
|
||||
PUBLIC void enable ();
|
||||
PUBLIC std::string root_for_path (std::string const& path);
|
||||
PUBLIC info_ptr info (std::string path);
|
||||
PUBLIC void wait_for_status (info_ptr info);
|
||||
|
||||
} /* ng */ } /* scm */
|
||||
|
||||
#endif /* end of include guard: SCM_NG_H_FXJGXN9B */
|
||||
@@ -1,6 +1,6 @@
|
||||
SOURCES = src/**/*.cc
|
||||
TESTS = tests/t_*.cc
|
||||
CP_Resources = resources/*
|
||||
EXPORT = src/{scm{,_ng},status,snapshot}.h
|
||||
EXPORT = src/{scm,status,snapshot}.h
|
||||
LINK += text cf io settings
|
||||
FRAMEWORKS = Carbon Security
|
||||
|
||||
Reference in New Issue
Block a user