Skip to content

Instantly share code, notes, and snippets.

@DartPower
Created December 29, 2025 16:28
Show Gist options
  • Select an option

  • Save DartPower/e0cc42379933b3410057aca11469f153 to your computer and use it in GitHub Desktop.

Select an option

Save DartPower/e0cc42379933b3410057aca11469f153 to your computer and use it in GitHub Desktop.
Git Commit History Exporter in C# (Экспорт истории Git)
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;
namespace GitCommitExporter
{
public class CommitInfo
{
public string Hash;
public DateTime Date;
public string Tag;
public string BranchName;
public string GitRef;
public string Subject;
}
public enum ExportFormat
{
Subfolders,
Zips
}
public enum SortOrder
{
Asc,
Desc
}
[Flags]
public enum MetadataType
{
None = 0,
Json = 1,
Xml = 2,
Log = 4
}
class Program
{
private static string _logFilePath = null;
private static string _tempGitConfigPath = null;
static void Main(string[] args)
{
MetadataWriter metadataWriter = null;
try
{
SetupGitEnvironment();
SetupLogging();
if (args.Length < 1)
{
LogUsage();
return;
}
// 1. Парсинг путей
bool isUrl = IsUrl(args[0]);
string repoPath = "";
string outputPath = "";
int optionsStartIndex = 0;
if (isUrl)
{
if (args.Length < 3) { LogError("Для URL нужны 2 пути: клонирование и экспорт."); LogUsage(); return; }
string url = args[0];
repoPath = Path.GetFullPath(args[1]);
outputPath = Path.GetFullPath(args[2]);
optionsStartIndex = 3;
LogMessage("URL: " + url);
CloneRepository(url, repoPath);
}
else
{
if (args.Length < 2) { LogUsage(); return; }
repoPath = Path.GetFullPath(args[0]);
outputPath = Path.GetFullPath(args[1]);
optionsStartIndex = 2;
if (!Directory.Exists(repoPath)) { LogError("Репозиторий не найден: " + repoPath); return; }
}
// 2. Парсинг опций
var parsedArgs = ParseArguments(args, optionsStartIndex);
// Basic
string formatStr = parsedArgs.ContainsKey("format") ? parsedArgs["format"] : "subfolders";
var format = (ExportFormat)Enum.Parse(typeof(ExportFormat), formatStr, true);
string orderStr = parsedArgs.ContainsKey("order") ? parsedArgs["order"] : "asc";
var order = (SortOrder)Enum.Parse(typeof(SortOrder), orderStr, true);
string startFromHash = parsedArgs.ContainsKey("continue") ? parsedArgs["continue"] : null;
string toHash = parsedArgs.ContainsKey("to_hash") ? parsedArgs["to_hash"] : null;
// Filters
DateTime? fromDate = null;
if (parsedArgs.ContainsKey("from_date")) try { fromDate = DateTime.Parse(parsedArgs["from_date"]); } catch { }
DateTime? toDate = null;
if (parsedArgs.ContainsKey("to_date")) try { toDate = DateTime.Parse(parsedArgs["to_date"]); } catch { }
int limitCount = parsedArgs.ContainsKey("limit_count") ? int.Parse(parsedArgs["limit_count"]) : int.MaxValue;
int limitSizeMB = parsedArgs.ContainsKey("limit_size_mb") ? int.Parse(parsedArgs["limit_size_mb"]) : int.MaxValue;
// Metadata Settings (Log enabled by default)
MetadataType exportMeta = MetadataType.Log;
if (parsedArgs.ContainsKey("export"))
{
foreach (string p in parsedArgs["export"].Split(new char[] { ',' }))
{
string val = p.Trim().ToLower();
if (val == "xml") exportMeta |= MetadataType.Xml;
if (val == "json") exportMeta |= MetadataType.Json;
if (val == "log") exportMeta |= MetadataType.Log;
}
}
LogMessage("Выход: " + outputPath);
LogMessage("Формат: " + format + ", Порядок: " + order);
LogMessage("Лимиты: Кол-во=" + (limitCount == int.MaxValue ? "∞" : limitCount.ToString()) +
", Размер=" + (limitSizeMB == int.MaxValue ? "∞" : limitSizeMB + "MB"));
Directory.CreateDirectory(outputPath);
// 3. Инициализация метаданных и Tee-лога
string repoName = Path.GetFileName(repoPath.TrimEnd('\\', '/'));
metadataWriter = new MetadataWriter(outputPath, repoName, exportMeta);
metadataWriter.Open();
bool useRemoteBranches = isUrl;
ExportAllCommits(repoPath, outputPath, format, order, startFromHash, toHash, fromDate, toDate, limitCount, limitSizeMB, useRemoteBranches, metadataWriter);
LogMessage("Экспорт завершен успешно.");
}
catch (Exception ex)
{
LogError("Критическая ошибка: " + ex.Message);
}
finally
{
if (metadataWriter != null) metadataWriter.Close();
if (Trace.Listeners.Count > 1) ((TextWriterTraceListener)Trace.Listeners[1]).Flush();
CleanupGitEnvironment();
}
}
private static bool IsUrl(string input)
{
if (IsNullOrWhiteSpace(input)) return false;
string lower = input.ToLower();
return lower.StartsWith("http://") || lower.StartsWith("https://") || lower.StartsWith("git@");
}
private static void CloneRepository(string url, string targetPath)
{
LogMessage("Клонирование...");
if (Directory.Exists(targetPath))
{
if (Directory.Exists(Path.Combine(targetPath, ".git")))
{
RunGitCommand(targetPath, "fetch --all");
RunGitCommand(targetPath, "fetch --tags");
return;
}
}
RunGitCommand(Path.GetDirectoryName(targetPath), string.Format("clone --recursive \"{0}\" \"{1}\"", url, targetPath));
RunGitCommand(targetPath, "fetch --all");
RunGitCommand(targetPath, "fetch --tags");
}
private static int RunGitCommand(string workDir, string arguments)
{
ProcessStartInfo psi = new ProcessStartInfo("git", arguments);
psi.WorkingDirectory = workDir;
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
psi.CreateNoWindow = true;
ApplyGitEnvironment(psi);
using (Process p = Process.Start(psi))
{
p.WaitForExit();
if (p.ExitCode != 0) LogError("Git Err: " + p.StandardError.ReadToEnd());
return p.ExitCode;
}
}
// --- TeeTraceListener для export=log ---
private class TeeTraceListener : TraceListener
{
private string _logPath;
private object _lock = new object();
public TeeTraceListener(string logPath)
{
_logPath = logPath;
Directory.CreateDirectory(Path.GetDirectoryName(_logPath));
try { File.WriteAllText(_logPath, "EXPORT LOG STARTED: " + DateTime.Now + "\r\n"); }
catch { }
}
public override void Write(string message)
{
try { lock (_lock) { File.AppendAllText(_logPath, message); } } catch { }
}
public override void WriteLine(string message)
{
try { lock (_lock) { File.AppendAllText(_logPath, message + "\r\n"); } } catch { }
}
}
// --- MetadataWriter ---
public class MetadataWriter : IDisposable
{
private string _outputPath;
private string _repoName;
private MetadataType _type;
private StreamWriter _jsonWriter = null;
private XmlTextWriter _xmlWriter = null;
public MetadataWriter(string outputPath, string repoName, MetadataType type)
{
_outputPath = outputPath;
_repoName = repoName;
_type = type;
}
public void Open()
{
if ((_type & MetadataType.Json) != 0)
{
string path = Path.Combine(_outputPath, _repoName + "_metadata.json");
_jsonWriter = new StreamWriter(path, true, Encoding.UTF8);
HealBrokenFile(_jsonWriter.BaseStream, "}\n");
}
if ((_type & MetadataType.Xml) != 0)
{
string path = Path.Combine(_outputPath, _repoName + "_metadata.xml");
_xmlWriter = new XmlTextWriter(path, Encoding.UTF8);
_xmlWriter.Formatting = Formatting.Indented;
_xmlWriter.Indentation = 2;
HealBrokenFile(_xmlWriter.BaseStream, ">\r\n");
}
if ((_type & MetadataType.Log) != 0)
{
string logPath = Path.Combine(_outputPath, _repoName + "_run.log");
Trace.Listeners.Add(new TeeTraceListener(logPath));
}
}
private void HealBrokenFile(Stream stream, string expectedEndPattern)
{
try
{
long len = stream.Length;
if (len == 0) return;
int readSize = 1024;
if (len < readSize) readSize = (int)len;
stream.Seek(-readSize, SeekOrigin.End);
byte[] buffer = new byte[readSize];
int bytesRead = stream.Read(buffer, 0, readSize);
string tail = Encoding.UTF8.GetString(buffer, 0, bytesRead);
int lastLineIdx = tail.LastIndexOf('\n');
if (lastLineIdx < 0 || lastLineIdx < tail.Length - 1)
{
long newLen = stream.Position - (tail.Length - (lastLineIdx < 0 ? 0 : lastLineIdx + 1));
stream.SetLength(newLen);
}
}
catch { }
}
public void WriteEntry(CommitInfo commit, string filePath, long size)
{
try
{
if (_jsonWriter != null)
{
_jsonWriter.WriteLine("{{\"Repo\":\"{0}\",\"Branch\":\"{1}\",\"Hash\":\"{2}\",\"Date\":\"{3}\",\"Subject\":\"{4}\",\"Tag\":\"{5}\",\"Path\":\"{6}\",\"Size\":{7}}}",
EscapeJson(_repoName), EscapeJson(commit.BranchName), commit.Hash, commit.Date.ToString("o"), EscapeJson(commit.Subject),
EscapeJson(commit.Tag ?? ""), EscapeJson(filePath), size);
_jsonWriter.Flush();
}
if (_xmlWriter != null)
{
_xmlWriter.WriteStartElement("Item");
_xmlWriter.WriteAttributeString("Repo", _repoName);
_xmlWriter.WriteAttributeString("Branch", commit.BranchName);
_xmlWriter.WriteAttributeString("Hash", commit.Hash);
_xmlWriter.WriteAttributeString("Date", commit.Date.ToString("o"));
_xmlWriter.WriteAttributeString("Subject", commit.Subject);
_xmlWriter.WriteAttributeString("Tag", commit.Tag ?? "");
_xmlWriter.WriteAttributeString("Path", filePath);
_xmlWriter.WriteAttributeString("Size", size.ToString());
_xmlWriter.WriteEndElement();
_xmlWriter.WriteWhitespace("\r\n");
_xmlWriter.Flush();
}
}
catch { }
}
private string EscapeJson(string s)
{
if (s == null) return "";
return s.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n").Replace("\r", "\\r");
}
public void Close()
{
try { if (_jsonWriter != null) { _jsonWriter.Flush(); _jsonWriter.Close(); } } catch { }
try { if (_xmlWriter != null) { _xmlWriter.Flush(); _xmlWriter.Close(); } } catch { }
}
public void Dispose() { Close(); }
}
// --- Проверка целостности (упрощенная и надежная) ---
private static bool VerifyExportIntegrity(string path, CommitInfo commit, ExportFormat format)
{
if ((format == ExportFormat.Zips && !File.Exists(path)) ||
(format == ExportFormat.Subfolders && !Directory.Exists(path)))
{
return false;
}
// Проверка размера
try
{
long size = 0;
if (format == ExportFormat.Zips) size = new FileInfo(path).Length;
else
{
// Для папок считаем размер, если папка пустая - что-то не так
// Но для скорости можно просто проверить наличие папки
DirectoryInfo di = new DirectoryInfo(path);
if (!di.Exists) return false;
}
if (format == ExportFormat.Zips && size == 0) return false;
}
catch { return false; }
// Проверка даты
try
{
DateTime fileDate;
if (format == ExportFormat.Zips) fileDate = File.GetLastWriteTime(path);
else fileDate = Directory.GetLastWriteTime(path);
// Допуск разницы в 1 секунду
if (Math.Abs((fileDate - commit.Date).TotalSeconds) > 2)
{
// Если дата не совпадает, считаем, что экспорт не был завершен (свет вырубился во время записи)
return false;
}
}
catch { return false; }
// УБРАЛИ ПРОВЕРКУ ЧЕРЕЗ SHELL.APPLICATION.
// Она вызывала ложные срабатывания на валидных файлах.
// Для .NET 2.0 без библиотек лучше полагаться на размер и дату.
// Если git archive завершился успешно, и дата проставилась - файл ок.
return true;
}
// --- Вспомогательные методы ---
private static bool IsNullOrWhiteSpace(string value)
{
if (value == null) return true;
for (int i = 0; i < value.Length; i++) if (!char.IsWhiteSpace(value[i])) return false;
return true;
}
private static long GetDirectorySize(string path)
{
try
{
DirectoryInfo di = new DirectoryInfo(path);
long size = 0;
FileInfo[] files = di.GetFiles("*", SearchOption.AllDirectories);
foreach (FileInfo fi in files) size += fi.Length;
return size;
}
catch { return 0; }
}
private static string SanitizeFileName(string name)
{
if (IsNullOrWhiteSpace(name)) return "unknown";
char[] invalidChars = Path.GetInvalidFileNameChars();
string sanitizedName = string.Join("_", name.Split(invalidChars));
sanitizedName = sanitizedName.Trim().TrimEnd('.');
return string.IsNullOrEmpty(sanitizedName) ? "unknown" : sanitizedName;
}
private static void SetupGitEnvironment()
{
try { _tempGitConfigPath = Path.Combine(Path.GetTempPath(), "git_cfg_" + Guid.NewGuid() + ".tmp"); File.WriteAllText(_tempGitConfigPath, "[safe]\n\tdirectory = *"); }
catch { }
}
private static void CleanupGitEnvironment() { try { if (_tempGitConfigPath != null && File.Exists(_tempGitConfigPath)) File.Delete(_tempGitConfigPath); } catch { } }
private static void ApplyGitEnvironment(ProcessStartInfo psi) { if (_tempGitConfigPath != null) { psi.EnvironmentVariables["GIT_CONFIG_GLOBAL"] = _tempGitConfigPath; psi.EnvironmentVariables["GIT_CONFIG_NOSYSTEM"] = "1"; } }
private static List<string> GetBranches(string repoPath, bool useRemotes)
{
var list = new List<string>();
ProcessStartInfo psi = new ProcessStartInfo("git", useRemotes ? "branch -r" : "branch --format=\"%(refname:short)\"");
psi.WorkingDirectory = repoPath;
psi.UseShellExecute = false; psi.RedirectStandardOutput = true; psi.CreateNoWindow = true;
ApplyGitEnvironment(psi);
using (Process p = Process.Start(psi))
{
string outp = p.StandardOutput.ReadToEnd(); p.WaitForExit();
foreach (string l in outp.Split(new char[] { '\n' })) if (!IsNullOrWhiteSpace(l) && !l.Contains("->")) list.Add(l.Trim());
}
return list;
}
private static Dictionary<string, string> ParseArguments(string[] args, int start)
{
var d = new Dictionary<string, string>();
for (int i = start; i < args.Length; i++)
{
if (args[i].IndexOf('=') >= 0)
{
string[] p = args[i].Split(new char[] { '=' }, 2);
d[p[0]] = p[1];
}
else
{
string v = args[i].ToLower();
if (v == "zips" || v == "subfolders") d["format"] = v;
else if (v == "asc" || v == "desc") d["order"] = v;
else d[args[i]] = "true";
}
}
return d;
}
private static void SetupLogging()
{
try
{
Directory.CreateDirectory("Logs");
_logFilePath = "Logs\\Git_" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".log";
Trace.Listeners.Add(new ConsoleTraceListener());
Trace.Listeners.Add(new TextWriterTraceListener(_logFilePath));
Trace.AutoFlush = true;
}
catch { }
}
private static void LogMessage(string m) { Trace.WriteLine("[" + DateTime.Now.ToString("HH:mm:ss") + "] " + m); }
private static void LogError(string m) { Trace.WriteLine("[" + DateTime.Now.ToString("HH:mm:ss") + "] ERR: " + m); }
private static void LogUsage()
{
string u = "Использование: GitCommitExporter.exe <input> <output> [options]\n" +
" input: URL или локальный путь\n" +
" output: Путь для экспорта\n" +
"Опции:\n" +
" format: subfolders | zips\n" +
" order: asc | desc\n" +
" continue: hash (начать с)\n" +
" to_hash: hash (закончить на)\n" +
" from_date/to_date: YYYY-MM-DD\n" +
" limit_count: N\n" +
" limit_size_mb: N\n" +
" export: xml,json,log (по умолчанию log)\n";
Console.WriteLine(u);
}
private static void ExportAllCommits(string repoPath, string outputPath, ExportFormat format, SortOrder order,
string startHash, string toHash, DateTime? fromDate, DateTime? toDate, int limitCount, int limitSizeMB,
bool useRemotes, MetadataWriter metaWriter)
{
var branches = GetBranches(repoPath, useRemotes);
int globalProcessed = 0;
long globalSizeBytes = 0;
bool stopRequested = false;
foreach (var branchRef in branches)
{
if (stopRequested) break;
string branchName = branchRef.Contains("/") ? branchRef.Substring(branchRef.LastIndexOf('/') + 1) : branchRef;
LogMessage("\n--- Ветка: " + branchName + " ---");
var allCommits = GetGitCommits(repoPath, branchRef, order);
List<CommitInfo> toProcess = new List<CommitInfo>();
// Применение фильтров
bool startFound = string.IsNullOrEmpty(startHash);
foreach (var c in allCommits)
{
if (!startFound)
{
if (c.Hash.StartsWith(startHash, StringComparison.OrdinalIgnoreCase)) startFound = true;
else continue;
}
if (!string.IsNullOrEmpty(toHash) && c.Hash.StartsWith(toHash, StringComparison.OrdinalIgnoreCase))
{
toProcess.Add(c);
break;
}
if (!string.IsNullOrEmpty(toHash)) continue;
if (fromDate.HasValue && c.Date < fromDate.Value) continue;
if (toDate.HasValue && c.Date > toDate.Value) continue;
toProcess.Add(c);
}
int skipped = 0;
// ИСПРАВЛЕНИЕ СЧЕТЧИКА: показываем прогресс внутри ветки
for (int i = 0; i < toProcess.Count; i++)
{
var c = toProcess[i];
if (globalProcessed >= limitCount) { LogMessage("Достигнут лимит count."); stopRequested = true; break; }
if (format == ExportFormat.Zips && limitSizeMB != int.MaxValue)
{
double sizeMB = globalSizeBytes / (1024.0 * 1024.0);
if (sizeMB >= limitSizeMB) { LogMessage("Достигнут лимит size."); stopRequested = true; break; }
}
string safeBranch = SanitizeFileName(branchName);
string safeTag = string.IsNullOrEmpty(c.Tag) ? "" : "_" + SanitizeFileName(c.Tag);
string baseName = Path.GetFileName(outputPath.TrimEnd('\\', '/')) + "_" + safeBranch + "_tree_" + c.Hash + "_" + c.Date.ToString("yyyyMMdd_HHmmss") + safeTag;
string targetPath = Path.Combine(outputPath, baseName + (format == ExportFormat.Zips ? ".zip" : ""));
bool isValid = false;
if ((format == ExportFormat.Zips && File.Exists(targetPath)) ||
(format == ExportFormat.Subfolders && Directory.Exists(targetPath)))
{
if (VerifyExportIntegrity(targetPath, c, format))
{
isValid = true;
skipped++;
}
else
{
try
{
if (format == ExportFormat.Zips)
{
string badPath = targetPath + "_probably_corrupted";
if (File.Exists(badPath)) File.Delete(badPath);
File.Move(targetPath, badPath);
}
else
{
Directory.Move(targetPath, targetPath + "_probably_corrupted");
}
}
catch { }
}
}
if (!isValid)
{
// ИСПРАВЛЕНИЕ ЛОГИРОВАНИЯ: Используем Trace.Write, чтобы попадало в логи
// Формат: [Текущий/Всего] Хэш - Тема
string progressInfo = string.Format("[{0}/{1}] {2} - {3}", i + 1, toProcess.Count, c.Hash.Substring(0, 7), c.Subject);
if (format == ExportFormat.Zips)
{
Trace.Write(progressInfo + " ... ");
}
else
{
LogMessage(progressInfo);
}
try
{
if (format == ExportFormat.Subfolders) ExportToFolder(repoPath, c.Hash, targetPath);
else
{
ExportToZip(repoPath, c.Hash, targetPath, c.Date);
Trace.WriteLine("ОК"); // Trace.WriteLine, чтобы попало в логи
}
long size = 0;
try
{
if (format == ExportFormat.Zips) size = new FileInfo(targetPath).Length;
else size = GetDirectorySize(targetPath);
globalSizeBytes += size;
globalProcessed++;
if (metaWriter != null) metaWriter.WriteEntry(c, targetPath, size);
}
catch (Exception metaEx) { LogError("Ошибка записи метаданных: " + metaEx.Message); }
}
catch (Exception ex)
{
if (format == ExportFormat.Zips) Trace.WriteLine("FAIL");
LogError("Ошибка экспорта " + c.Hash + ": " + ex.Message);
}
}
}
LogMessage("Ветка завершена. Пропущено: " + skipped);
}
}
private static List<CommitInfo> GetGitCommits(string repoPath, string branchRef, SortOrder order)
{
var list = new List<CommitInfo>();
ProcessStartInfo psi = new ProcessStartInfo("git", "log " + (order == SortOrder.Asc ? "--reverse " : "") + branchRef + " --decorate=short --pretty=format:\"%H|%ci|%D|%s\"");
psi.WorkingDirectory = repoPath; psi.UseShellExecute = false; psi.RedirectStandardOutput = true; psi.CreateNoWindow = true;
ApplyGitEnvironment(psi);
using (Process p = Process.Start(psi))
{
string outp = p.StandardOutput.ReadToEnd(); p.WaitForExit();
foreach (string line in outp.Split(new char[] { '\n' }))
{
if (!IsNullOrWhiteSpace(line))
{
string[] parts = line.Split(new char[] { '|' });
if (parts.Length >= 2)
{
var c = new CommitInfo();
c.Hash = parts[0];
try { c.Date = DateTime.Parse(parts[1]); } catch { c.Date = DateTime.MinValue; }
c.Subject = parts.Length >= 4 ? parts[3] : "";
c.BranchName = branchRef.Contains("/") ? branchRef.Substring(branchRef.LastIndexOf('/') + 1) : branchRef;
c.GitRef = branchRef;
if (parts.Length >= 3 && !IsNullOrWhiteSpace(parts[2]))
{
foreach (string r in parts[2].Split(',')) if (r.Trim().StartsWith("tag: ")) { c.Tag = r.Trim().Substring(5); break; }
}
list.Add(c);
}
}
}
}
return list;
}
private static void ExportToFolder(string repoPath, string hash, string outPath)
{
string tmp = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".zip");
try { CreateArchive(repoPath, hash, tmp); Unzip(tmp, outPath); }
finally { if (File.Exists(tmp)) File.Delete(tmp); }
}
private static void ExportToZip(string repoPath, string hash, string outPath, DateTime date)
{
CreateArchive(repoPath, hash, outPath);
try { File.SetCreationTime(outPath, date); File.SetLastWriteTime(outPath, date); } catch { }
}
private static void CreateArchive(string repoPath, string hash, string path)
{
ProcessStartInfo psi = new ProcessStartInfo("git", "archive --format=zip --output=\"" + path + "\" " + hash);
psi.WorkingDirectory = repoPath; psi.UseShellExecute = false; psi.RedirectStandardError = true; psi.CreateNoWindow = true;
ApplyGitEnvironment(psi);
using (Process p = Process.Start(psi))
{
p.WaitForExit();
if (p.ExitCode != 0) throw new Exception("Git archive fail: " + p.StandardError.ReadToEnd());
}
}
private static void Unzip(string zipPath, string targetPath)
{
if (!Directory.Exists(targetPath)) Directory.CreateDirectory(targetPath);
Type t = Type.GetTypeFromProgID("Shell.Application");
object s = Activator.CreateInstance(t);
object src = t.InvokeMember("NameSpace", BindingFlags.InvokeMethod, null, s, new object[] { zipPath });
object dst = t.InvokeMember("NameSpace", BindingFlags.InvokeMethod, null, s, new object[] { targetPath });
object items = src.GetType().InvokeMember("Items", BindingFlags.GetProperty, null, src, null);
int cnt = (int)items.GetType().InvokeMember("Count", BindingFlags.GetProperty, null, items, null);
dst.GetType().InvokeMember("CopyHere", BindingFlags.InvokeMethod, null, dst, new object[] { items, 4 | 16 | 1024 });
int wait = 0;
while (wait < 120)
{
System.Threading.Thread.Sleep(500);
object dItems = dst.GetType().InvokeMember("Items", BindingFlags.GetProperty, null, dst, null);
int dCnt = (int)dItems.GetType().InvokeMember("Count", BindingFlags.GetProperty, null, dItems, null);
if (dCnt >= cnt) { System.Threading.Thread.Sleep(500); break; }
wait++;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment