public void Clone(object args) //(string repopath)
        {
            ThreadArgs   threadargs = (ThreadArgs)args;
            CloneOptions options    = new CloneOptions();

            options.OnTransferProgress = GitProgress;
            try
            {
                if (!File.Exists(threadargs.RepoPath))
                {
                    var info = Directory.CreateDirectory(threadargs.RepoPath);
                    Console.WriteLine($"Created this directory for Clone(): {info.FullName}");
                }

                // Repository repository = new Repository();
                // repository.Config.Set("core.autocrlf", "auto");
                string repopath = Repository.Clone(mRemoteGitRepository, threadargs.RepoPath, options);
                Console.WriteLine($"Clone returned {repopath}");
            }
            catch (Exception e)
            {
                if (!mIsShuttingDown)
                {
                    threadargs.callbackError?.Invoke(e);
                }
                return;
            }
            if (!mIsShuttingDown)
            {
                threadargs.callbackComplete?.Invoke(args);
            }

            //m_dtCloneEnd = DateTime.Now;
        }
        private void ThreadComplete(object args)
        {
            ThreadArgs threadargs = (ThreadArgs)args;

            // TODO: remove from threadlist

            mCacheCreateThreads.RemoveAll(item => item.ThreadID == threadargs.ThreadID);

            var repoCacheList = BorrowRepoCacheStates(threadargs);

            try
            {
                for (int i = 0; i < repoCacheList.Count; i++)
                {
                    if (repoCacheList[i].Path == threadargs.RepoPath)
                    {
                        repoCacheList[i].CloneCompleted = true;
                        var nAvailableCaches = repoCacheList.Where(x => x.CloneCompleted).Count();
                        mAvailableCaches = nAvailableCaches;
                        mCallbackInfo?.Invoke($"Cache Clone Completed ({threadargs.ThreadID}). Available Caches: {nAvailableCaches}/{mCacheSize}", nAvailableCaches);
                        SaveCacheState(repoCacheList);

                        break;
                    }
                }
            }
            finally { ReturnRepoCacheState(threadargs); }
        }
        private void ManageCaches(Object notused)
        {
            // checks that the number of available caches match the cache size and creates caches if needed

            // for existing caches, it checks that the cache has been cloned and, if so, ensures each cache has been git pulled today.
            if (mCacheCreateThreads.Count > 0)
            {
                return;                                // only want 1 git clone occuring
            }
            var repoCacheList = BorrowRepoCacheStates(null);

            try
            {
                if (repoCacheList.Count < mCacheSize)
                {
                    // not enough caches available, we need to create some

                    RepoCacheState newCacheState = new RepoCacheState();
                    string         cacheid       = Guid.NewGuid().ToString().Substring(0, 4);

                    string cachefolder = $"{mRootFolder}\\{REPOCACHE_FOLDER}\\{cacheid}";
                    if (Directory.Exists(cachefolder))
                    {
                        Directory.Delete(cachefolder);
                    }

                    var info = Directory.CreateDirectory(cachefolder);
                    Console.WriteLine(info.FullName);

                    var args = new ThreadArgs();
                    args.ThreadID                = $"{cachefolder}-clone";
                    args.RepoPath                = cachefolder;
                    args.ThreadInstance          = new Thread(Clone);
                    args.ThreadInstance.Priority = ThreadPriority.BelowNormal;
                    args.callbackError           = ThreadException;
                    args.callbackComplete        = ThreadComplete;
                    args.ThreadInstance.Name     = args.ThreadID;
                    args.ThreadInstance.Start(args);
                    mProgressMsgCounter = 0;

                    int nAvailableCaches = repoCacheList.Where(x => x.CloneCompleted).Count();

                    mCallbackInfo?.Invoke($"Preparing new cache. Available caches = {nAvailableCaches}/{mCacheSize}", nAvailableCaches);
                    mCacheCreateThreads.Add(args);

                    ///
                    newCacheState.Path = cachefolder;
                    repoCacheList.Add(newCacheState);
                }
            }
            finally
            {
                ReturnRepoCacheState(null);
            }
        }
        private void ReturnRepoCacheState(ThreadArgs theReturningThread)
        {
            string sThreadId = "main thread";

            if (theReturningThread != null)
            {
                sThreadId = theReturningThread.ThreadID;
            }

            Console.WriteLine($"{sThreadId} has released mCacheStateShared");
            mCacheStateMutex.ReleaseMutex();
        }
        private List <RepoCacheState> BorrowRepoCacheStates(ThreadArgs theRequestingThread)
        {
            string sThreadId = "main thread";

            if (theRequestingThread != null)
            {
                sThreadId = theRequestingThread.ThreadID;
            }
            Console.WriteLine($"{sThreadId} is waiting for mCacheStateShared");
            mCacheStateMutex.WaitOne();
            Console.WriteLine($"{sThreadId} has received mCacheStateShared");
            return(mSharedRepoCacheList);
        }