PageRenderTime 46ms CodeModel.GetById 1ms app.highlight 40ms RepoModel.GetById 1ms app.codeStats 0ms

/win32/shellext/QueryDirstate.cpp

https://bitbucket.org/tortoisehg/hgtk/
C++ | 372 lines | 343 code | 13 blank | 16 comment | 8 complexity | 2a962b1053de06a420a46a9aa2ed9505 MD5 | raw file
  1
  2// Copyright (C) 2009 Benjamin Pollack
  3// Copyright (C) 2009 Adrian Buehlmann
  4//
  5// This program is free software: you can redistribute it and/or modify
  6// it under the terms of the GNU General Public License as published by
  7// the Free Software Foundation, either version 2 of the License, or
  8// (at your option) any later version.
  9//
 10// This program is distributed in the hope that it will be useful,
 11// but WITHOUT ANY WARRANTY; without even the implied warranty of
 12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13// GNU General Public License for more details.
 14//
 15// You should have received a copy of the GNU General Public License
 16// along with this program.  If not, see <http://www.gnu.org/licenses/>.
 17
 18#include "stdafx.h"
 19
 20#include "QueryDirstate.h"
 21#include "dirstate.h"
 22#include "DirectoryStatus.h"
 23#include "Dirstatecache.h"
 24#include "Winstat.h"
 25#include "TortoiseUtils.h"
 26#include "Thgstatus.h"
 27
 28#include <shlwapi.h>
 29
 30
 31class QueryState
 32{
 33public:
 34    std::string path;
 35    bool        isdir;
 36    std::string basedir;
 37    std::string hgroot;
 38
 39    char        status;
 40    unsigned    tickcount;
 41
 42    QueryState(): isdir(false), status('0'), tickcount(0) {}
 43};
 44
 45
 46bool hasHgDir(char cls, const std::string& path, unsigned& ticks)
 47{
 48    ticks = 0;
 49
 50    bool res = false;
 51
 52    if (path.empty() || path == "\\")
 53        return res;
 54
 55    const std::string p = path + "\\.hg";
 56
 57    if (::PathIsUNCServerShare(p.c_str()))
 58        return res;
 59
 60    unsigned tc0 = ::GetTickCount();
 61    res = ::PathIsDirectory(p.c_str()) != 0;
 62    unsigned tc1 = ::GetTickCount();
 63
 64    ticks = tc1 - tc0;
 65
 66    if (ticks > 5 /* ms */)
 67    {
 68        // trace slower PathIsDirectory calls (untypical on local discs)
 69        TDEBUG_TRACE(
 70            "[" << cls << "] hasHgDir: PathIsDirectory(\"" << p << "\")" << 
 71            " -> " << res << ", in " << ticks << " ticks"
 72        );
 73    }
 74
 75    return res;
 76}
 77
 78
 79int findHgRoot(char cls, QueryState& cur, QueryState& last, bool outdated)
 80{
 81    std::string dp = "["; dp += cls; dp += "] findHgRoot"; 
 82
 83    {
 84        std::string p = cur.path;
 85        p.push_back('\\');
 86        if (p.find("\\.hg\\") != std::string::npos) 
 87        {
 88            // ignore files and dirs named '.hg'
 89            last = cur;
 90            return 0;
 91        }
 92    }
 93
 94    if (!outdated && !last.hgroot.empty() 
 95        && cur.path.size() >= last.hgroot.size()
 96        && StartsWith(cur.path, last.hgroot + "\\"))
 97    {
 98        cur.hgroot = last.hgroot;
 99        return 1;
100    }
101
102    unsigned ticks = 0;
103    bool file_access_is_unacceptably_slow = false;
104
105    if (!PathIsNetworkPath(cur.path.c_str()))
106    {
107        // checking if we have a repo root, visible from its parent dir
108
109        const bool has_hg = hasHgDir(cls, cur.path, ticks);
110
111        if (ticks > 5000 /* ms */)
112        {
113            file_access_is_unacceptably_slow = true;
114            goto exit;
115        }
116
117        if (has_hg)
118        {
119            cur.hgroot = cur.path;
120            TDEBUG_TRACE(dp << "(" << cur.path << "): hgroot = cur.path");
121            return 1;
122        }
123    }
124
125    cur.basedir = DirName(cur.path);
126
127    if (!outdated && !last.basedir.empty() && cur.basedir == last.basedir)
128    {
129        cur.hgroot = last.hgroot;
130        return 1;
131    }
132
133    for (std::string p = cur.basedir;;)
134    {
135        bool has_hg = hasHgDir(cls, p, ticks);
136        if (ticks > 5000 /* ms */)
137        {
138            const std::string reason = "ignoring slow \"" + p + "\"";
139            Thgstatus::error(reason);
140            file_access_is_unacceptably_slow = true;
141            goto exit;
142        }
143
144        if (has_hg) 
145        {
146            cur.hgroot = p;
147            TDEBUG_TRACE(
148                dp << "(" << cur.path << "): hgroot = '" << cur.hgroot
149                << "' (found repo)"
150            );
151            return 1;
152        }
153        std::string p2 = DirName(p);
154        if (p2.size() == p.size())
155            break;
156        p.swap(p2);
157    }
158
159exit:
160    if (file_access_is_unacceptably_slow)
161    {
162        TDEBUG_TRACE(
163            "******" << dp << "(" << cur.path << "): ignored, " 
164            << "call took too long (" << ticks << " ticks)");
165    }
166    else
167    {
168        TDEBUG_TRACE(dp << "(" << cur.path << "): NO repo found");
169    }
170    last = cur;
171    return 0;
172}
173
174
175int get_relpath(
176    const std::string& hgroot, 
177    const std::string& path,
178    std::string& res
179)
180{
181    size_t offset = hgroot.size();
182    if (offset == 0)
183        return 0;
184
185    if (offset > path.size())
186        return 0;
187
188    if (path[offset] == '\\')
189        offset++;
190    
191    const char* relpathptr = path.c_str() + offset;
192
193    res = relpathptr;
194    return 1;
195}
196
197
198int HgQueryDirstate(
199    const char cls,
200    const std::string& path,
201    const char& filterStatus, 
202    char& outStatus
203)
204{
205    std::string dp = "["; dp += cls; dp += "] HgQueryDirstate: "; 
206
207    static QueryState last;
208
209    if (path.empty())
210        return 0;
211
212    QueryState cur;
213
214    cur.path = path;
215    cur.tickcount = ::GetTickCount();
216
217    const bool outdated = cur.tickcount - last.tickcount > 2000;
218
219    if (!outdated && last.path == path) 
220    {
221        outStatus = last.status;
222        if (outStatus == 'P')
223            outStatus = 'M';
224        return 1;
225    }
226
227    if (::PathIsRoot(path.c_str()))
228    {
229        last = cur;
230        return 0;
231    }
232
233    if (findHgRoot(cls, cur, last, outdated) == 0)
234        return 0;
235
236    size_t offset = cur.hgroot.length();
237
238    if (offset == 0)
239    {
240        last = cur;
241        return 0;
242    }
243
244    if (path[offset] == '\\')
245        offset++;
246    const char* relpathptr = path.c_str() + offset;
247
248    std::string relpath = relpathptr;
249
250    for (size_t i = 0; i < relpath.size(); ++i)
251    {
252        if (relpath[i] == '\\')
253            relpath[i] = '/';
254    }
255
256    DirectoryStatus* pdirsta = DirectoryStatus::get(cur.hgroot, cur.basedir);
257    if (pdirsta && pdirsta->noicons())
258    {
259        last = cur;
260        return 0;
261    }
262
263    if (relpath.empty())
264    {
265        outStatus = (pdirsta ? pdirsta->status(relpath) : '?');
266        cur.status = outStatus;
267        cur.tickcount = ::GetTickCount();
268        last = cur;
269        return 1;
270    }
271
272    bool unset = false;
273
274    Dirstate* pds = Dirstatecache::get(cur.hgroot, cur.basedir, unset);
275    if (!pds)
276    {
277        TDEBUG_TRACE(
278            dp << "Dirstatecache::get(" << cur.hgroot 
279            << ") returns no Dirstate"
280        );
281        last = cur;
282        return 0;
283    }
284
285    Winstat stat;
286    if (0 != stat.lstat(path.c_str())) 
287    {
288        TDEBUG_TRACE(dp << "lstat(" << path << ") failed");
289        last = cur;
290        return 0;
291    }
292    cur.isdir = stat.isdir;
293#if 0
294    TDEBUG_TRACE(
295        dp << "stat.lstat(\"" << cur.path << "\") "
296        << "-> stat.isdir is " << stat.isdir
297    );
298#endif
299
300    if (cur.isdir)
301    {
302        if (!relpath.empty() && !pds->root().getdir(relpath))
303        {
304            last = cur;
305            return 0;  // unknown dir -> no icon
306        }
307
308        outStatus = (pdirsta ? pdirsta->status(relpath) : '?');
309        cur.status = outStatus;
310        cur.tickcount = ::GetTickCount();
311        last = cur;
312        return 1;
313    }
314
315    const Direntry* const e = pds->root().get(relpath);
316    if (!e) 
317    {
318        last = cur;
319        return 0;
320    }
321
322    outStatus = e->status(stat);
323
324    if (unset)
325        goto exit;
326
327    bool update = false;
328
329    if (outStatus == 'M')
330    {
331        std::string relbase;
332        if (pdirsta && get_relpath(cur.hgroot, cur.basedir, relbase))
333        {
334            TDEBUG_TRACE(dp << "relbase = '" << relbase << "'");
335
336            char basedir_status = pdirsta->status(relbase);
337            TDEBUG_TRACE(dp << "basedir_status = " << basedir_status);
338
339            if (basedir_status != 'M')
340                update = true;
341        }        
342    }
343    else if (outStatus == 'P')
344    {
345        static unsigned lasttickcount;
346
347        const unsigned tc = ::GetTickCount();
348        const bool outdated = tc - lasttickcount > 6000;
349
350        if (outdated) // protect against endless update loops
351        {
352            update = true;
353            lasttickcount = tc;
354        }
355
356        TDEBUG_TRACE(dp << "outStatus is 'P'");
357    }
358
359    if (update)
360    {
361        TDEBUG_TRACE(dp << "calling Thgstatus::update");
362        Thgstatus::update(path);
363    }
364
365  exit:
366    cur.status = outStatus;
367    if (outStatus == 'P')
368        outStatus = 'M';
369    cur.tickcount = ::GetTickCount();
370    last = cur;
371    return 1;
372}