CGitProgressList: Export report and notify methods
[TortoiseGit.git] / src / TortoiseProc / GitProgressList.cpp
blob6d5e1437e96c9a17a148df4fb43a92d156c1b283
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2013 - TortoiseGit
4 // Copyright (C) 2003-2008 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "GitProgressList.h"
22 #include "TortoiseProc.h"
23 #include "messagebox.h"
24 #include "GITProgressDlg.h"
25 #include "LogDlg.h"
26 #include "TGitPath.h"
27 #include "registry.h"
28 #include "GitStatus.h"
29 #include "AppUtils.h"
30 #include "PathUtils.h"
31 #include "StringUtils.h"
32 #include "TempFile.h"
33 #include "UnicodeUtils.h"
34 #include "SoundUtils.h"
35 #include "GitDiff.h"
36 #include "Hooks.h"
37 #include "DropFiles.h"
38 //#include "GitLogHelper.h"
39 #include "RegHistory.h"
40 //#include "ConflictResolveDlg.h"
41 #include "LogFile.h"
42 #include "ShellUpdater.h"
43 #include "IconMenu.h"
44 #include "BugTraqAssociations.h"
45 #include "patch.h"
46 #include "MassiveGitTask.h"
47 #include "SmartHandle.h"
48 #include "LoglistUtils.h"
50 BOOL CGitProgressList::m_bAscending = FALSE;
51 int CGitProgressList::m_nSortedColumn = -1;
53 #define TRANSFERTIMER 100
54 #define VISIBLETIMER 101
55 // CGitProgressList
57 enum GITProgressDlgContextMenuCommands
59 // needs to start with 1, since 0 is the return value if *nothing* is clicked on in the context menu
60 ID_COMPARE = 1,
61 ID_EDITCONFLICT,
62 ID_CONFLICTRESOLVE,
63 ID_CONFLICTUSETHEIRS,
64 ID_CONFLICTUSEMINE,
65 ID_LOG,
66 ID_OPEN,
67 ID_OPENWITH,
68 ID_EXPLORE,
69 ID_COPY
72 IMPLEMENT_DYNAMIC(CGitProgressList, CListCtrl)
74 CGitProgressList::CGitProgressList():CListCtrl()
75 , m_bCancelled(FALSE)
76 , m_pThread(NULL)
77 , m_bErrorsOccurred(false)
78 , m_bBare(false)
79 , m_bNoCheckout(false)
80 , m_AutoTag(GIT_REMOTE_DOWNLOAD_TAGS_AUTO)
81 , m_options(ProgOptNone)
82 , m_bSetTitle(false)
83 , m_pTaskbarList(nullptr)
85 m_pInfoCtrl = nullptr;
86 m_pAnimate = nullptr;
87 m_pProgControl = nullptr;
88 m_pProgressLabelCtrl = nullptr;
89 m_pPostWnd = nullptr;
92 CGitProgressList::~CGitProgressList()
94 for (size_t i = 0; i < m_arData.size(); ++i)
96 delete m_arData[i];
98 if(m_pThread != NULL)
100 delete m_pThread;
105 BEGIN_MESSAGE_MAP(CGitProgressList, CListCtrl)
106 ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnNMCustomdrawSvnprogress)
107 ON_NOTIFY_REFLECT(NM_DBLCLK, OnNMDblclkSvnprogress)
108 ON_NOTIFY_REFLECT(HDN_ITEMCLICK, OnHdnItemclickSvnprogress)
109 ON_NOTIFY_REFLECT(LVN_BEGINDRAG, OnLvnBegindragSvnprogress)
110 ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnLvnGetdispinfoSvnprogress)
111 ON_MESSAGE(WM_SHOWCONFLICTRESOLVER, OnShowConflictResolver)
112 ON_WM_SIZE()
113 ON_WM_TIMER()
114 ON_WM_CONTEXTMENU()
115 ON_WM_CLOSE()
116 END_MESSAGE_MAP()
118 void CGitProgressList::Cancel()
120 m_bCancelled = TRUE;
125 // CGitProgressList message handlers
128 LRESULT CGitProgressList::OnShowConflictResolver(WPARAM /*wParam*/, LPARAM /*lParam*/)
130 #if 0
131 CConflictResolveDlg dlg(this);
132 const svn_wc_conflict_description_t *description = (svn_wc_conflict_description_t *)lParam;
133 if (description)
135 dlg.SetConflictDescription(description);
136 if (m_pTaskbarList)
138 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_PAUSED);
140 if (dlg.DoModal() == IDOK)
142 if (dlg.GetResult() == svn_wc_conflict_choose_postpone)
144 // if the result is conflicted and the dialog returned IDOK,
145 // that means we should not ask again in case of a conflict
146 m_AlwaysConflicted = true;
147 ::SendMessage(GetDlgItem(IDC_NONINTERACTIVE)->GetSafeHwnd(), BM_SETCHECK, BST_CHECKED, 0);
150 m_mergedfile = dlg.GetMergedFile();
151 m_bCancelled = dlg.IsCancelled();
152 if (m_pTaskbarList)
153 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_INDETERMINATE);
154 return dlg.GetResult();
157 return svn_wc_conflict_choose_postpone;
158 #endif
159 return 0;
161 #if 0
162 svn_wc_conflict_choice_t CGitProgressList::ConflictResolveCallback(const svn_wc_conflict_description_t *description, CString& mergedfile)
164 // we only bother the user when merging
165 if (((m_Command == GitProgress_Merge)||(m_Command == GitProgress_MergeAll)||(m_Command == GitProgress_MergeReintegrate))&&(!m_AlwaysConflicted)&&(description))
167 // we're in a worker thread here. That means we must not show a dialog from the thread
168 // but let the UI thread do it.
169 // To do that, we send a message to the UI thread and let it show the conflict resolver dialog.
170 LRESULT dlgResult = ::SendMessage(GetSafeHwnd(), WM_SHOWCONFLICTRESOLVER, 0, (LPARAM)description);
171 mergedfile = m_mergedfile;
172 return (svn_wc_conflict_choice_t)dlgResult;
175 return svn_wc_conflict_choose_postpone;
177 #endif
178 void CGitProgressList::AddItemToList()
180 int totalcount = GetItemCount();
182 SetItemCountEx(totalcount+1, LVSICF_NOSCROLL|LVSICF_NOINVALIDATEALL);
183 // make columns width fit
184 if (iFirstResized < 30)
186 // only resize the columns for the first 30 or so entries.
187 // after that, don't resize them anymore because that's an
188 // expensive function call and the columns will be sized
189 // close enough already.
190 ResizeColumns();
191 ++iFirstResized;
194 // Make sure the item is *entirely* visible even if the horizontal
195 // scroll bar is visible.
196 int count = GetCountPerPage();
197 if (totalcount <= (GetTopIndex() + count + nEnsureVisibleCount + 2))
199 ++nEnsureVisibleCount;
200 m_bLastVisible = true;
202 else
204 nEnsureVisibleCount = 0;
205 if (IsIconic() == 0)
206 m_bLastVisible = false;
211 BOOL CGitProgressList::Notify(const CTGitPath& path, git_wc_notify_action_t action,
212 int /*status*/ ,
213 CString *strErr
215 svn_node_kind_t kind, const CString& mime_type,
216 svn_wc_notify_state_t content_state,
217 svn_wc_notify_state_t prop_state, LONG rev,
218 const svn_lock_t * lock, svn_wc_notify_lock_state_t lock_state,
219 const CString& changelistname,
220 svn_merge_range_t * range,
221 svn_error_t * err, apr_pool_t * pool
224 bool bNoNotify = false;
225 bool bDoAddData = true;
226 NotificationData * data = new NotificationData();
227 data->path = path;
228 data->action = action;
229 data->sPathColumnText=path.GetGitPathString();
230 data->bAuxItem = false;
232 if (this->m_pAnimate)
233 this->m_pAnimate->ShowWindow(SW_HIDE);
235 #if 0
236 data->kind = kind;
237 data->mime_type = mime_type;
238 data->content_state = content_state;
239 data->prop_state = prop_state;
240 data->rev = rev;
241 data->lock_state = lock_state;
242 data->changelistname = changelistname;
243 if ((lock)&&(lock->owner))
244 data->owner = CUnicodeUtils::GetUnicode(lock->owner);
245 data->sPathColumnText = path.GetUIPathString();
246 if (!m_basePath.IsEmpty())
247 data->basepath = m_basePath;
248 if (range)
249 data->merge_range = *range;
250 #endif
251 switch (data->action)
253 case git_wc_notify_add:
254 //case svn_wc_notify_update_add:
255 // if ((data->content_state == svn_wc_notify_state_conflicted) || (data->prop_state == svn_wc_notify_state_conflicted))
256 // {
257 // data->color = m_Colors.GetColor(CColors::Conflict);
258 // data->bConflictedActionItem = true;
259 // data->sActionColumnText.LoadString(IDS_SVNACTION_CONFLICTED);
260 // ++m_nConflicts;
261 // }
262 // else
263 // {
264 // m_bMergesAddsDeletesOccurred = true;
265 data->sActionColumnText.LoadString(IDS_SVNACTION_ADD);
266 data->color = m_Colors.GetColor(CColors::Added);
267 // }
268 break;
269 case git_wc_notify_sendmail_start:
270 data->bAuxItem = true;
271 data->sActionColumnText.LoadString(IDS_SVNACTION_SENDMAIL_START);
272 data->color = m_Colors.GetColor(CColors::Modified);
273 break;
275 case git_wc_notify_sendmail_error:
276 data->bAuxItem = true;
277 data->sActionColumnText.LoadString(IDS_SVNACTION_SENDMAIL_ERROR);
278 if(strErr)
279 data->sPathColumnText = *strErr;
280 else
281 data->sPathColumnText.Empty();
282 data->color = m_Colors.GetColor(CColors::Modified);
283 break;
285 case git_wc_notify_sendmail_done:
287 data->sActionColumnText.LoadString(IDS_SVNACTION_SENDMAIL_DONE);
288 data->sPathColumnText.Empty();
289 data->color = m_Colors.GetColor(CColors::Modified);
290 break;
292 case git_wc_notify_sendmail_retry:
293 data->sActionColumnText.LoadString(IDS_SVNACTION_SENDMAIL_RETRY);
294 data->sPathColumnText.Empty();
295 data->color = m_Colors.GetColor(CColors::Modified);
296 break;
299 case git_wc_notify_resolved:
300 data->sActionColumnText.LoadString(IDS_SVNACTION_RESOLVE);
301 break;
303 case git_wc_notify_revert:
304 data->sActionColumnText.LoadString(IDS_SVNACTION_REVERT);
305 break;
307 case git_wc_notify_checkout:
308 data->sActionColumnText.LoadString(IDS_PROGRS_CMD_CHECKOUT);
309 data->color = m_Colors.GetColor(CColors::Added);
310 data->bAuxItem = false;
311 break;
313 #if 0
314 case svn_wc_notify_commit_added:
315 data->sActionColumnText.LoadString(IDS_SVNACTION_ADDING);
316 data->color = m_Colors.GetColor(CColors::Added);
317 break;
318 case svn_wc_notify_copy:
319 data->sActionColumnText.LoadString(IDS_SVNACTION_COPY);
320 break;
321 case svn_wc_notify_commit_modified:
322 data->sActionColumnText.LoadString(IDS_SVNACTION_MODIFIED);
323 data->color = m_Colors.GetColor(CColors::Modified);
324 break;
325 case svn_wc_notify_delete:
326 case svn_wc_notify_update_delete:
327 data->sActionColumnText.LoadString(IDS_SVNACTION_DELETE);
328 m_bMergesAddsDeletesOccurred = true;
329 data->color = m_Colors.GetColor(CColors::Deleted);
330 break;
331 case svn_wc_notify_commit_deleted:
332 data->sActionColumnText.LoadString(IDS_SVNACTION_DELETING);
333 data->color = m_Colors.GetColor(CColors::Deleted);
334 break;
335 case svn_wc_notify_restore:
336 data->sActionColumnText.LoadString(IDS_SVNACTION_RESTORE);
337 break;
339 case svn_wc_notify_update_replace:
340 case svn_wc_notify_commit_replaced:
341 data->sActionColumnText.LoadString(IDS_SVNACTION_REPLACED);
342 data->color = m_Colors.GetColor(CColors::Deleted);
343 break;
344 case svn_wc_notify_exists:
345 if ((data->content_state == svn_wc_notify_state_conflicted) || (data->prop_state == svn_wc_notify_state_conflicted))
347 data->color = m_Colors.GetColor(CColors::Conflict);
348 data->bConflictedActionItem = true;
349 ++m_nConflicts;
350 data->sActionColumnText.LoadString(IDS_SVNACTION_CONFLICTED);
352 else if ((data->content_state == svn_wc_notify_state_merged) || (data->prop_state == svn_wc_notify_state_merged))
354 data->color = m_Colors.GetColor(CColors::Merged);
355 m_bMergesAddsDeletesOccurred = true;
356 data->sActionColumnText.LoadString(IDS_SVNACTION_MERGED);
358 else
359 data->sActionColumnText.LoadString(IDS_SVNACTION_EXISTS);
360 break;
361 case svn_wc_notify_update_update:
362 // if this is an inoperative dir change, don't show the notification.
363 // an inoperative dir change is when a directory gets updated without
364 // any real change in either text or properties.
365 if ((kind == svn_node_dir)
366 && ((prop_state == svn_wc_notify_state_inapplicable)
367 || (prop_state == svn_wc_notify_state_unknown)
368 || (prop_state == svn_wc_notify_state_unchanged)))
370 bNoNotify = true;
371 break;
373 if ((data->content_state == svn_wc_notify_state_conflicted) || (data->prop_state == svn_wc_notify_state_conflicted))
375 data->color = m_Colors.GetColor(CColors::Conflict);
376 data->bConflictedActionItem = true;
377 ++m_nConflicts;
378 data->sActionColumnText.LoadString(IDS_SVNACTION_CONFLICTED);
380 else if ((data->content_state == svn_wc_notify_state_merged) || (data->prop_state == svn_wc_notify_state_merged))
382 data->color = m_Colors.GetColor(CColors::Merged);
383 m_bMergesAddsDeletesOccurred = true;
384 data->sActionColumnText.LoadString(IDS_SVNACTION_MERGED);
386 else if (((data->content_state != svn_wc_notify_state_unchanged)&&(data->content_state != svn_wc_notify_state_unknown)) ||
387 ((data->prop_state != svn_wc_notify_state_unchanged)&&(data->prop_state != svn_wc_notify_state_unknown)))
389 data->sActionColumnText.LoadString(IDS_SVNACTION_UPDATE);
391 else
393 bNoNotify = true;
394 break;
396 if (lock_state == svn_wc_notify_lock_state_unlocked)
398 CString temp(MAKEINTRESOURCE(IDS_SVNACTION_UNLOCKED));
399 data->sActionColumnText += _T(", ") + temp;
401 break;
403 case svn_wc_notify_update_external:
404 // For some reason we build a list of externals...
405 m_ExtStack.AddHead(path.GetUIPathString());
406 data->sActionColumnText.LoadString(IDS_SVNACTION_EXTERNAL);
407 data->bAuxItem = true;
408 break;
410 case svn_wc_notify_update_completed:
412 data->sActionColumnText.LoadString(IDS_SVNACTION_COMPLETED);
413 data->bAuxItem = true;
414 bool bEmpty = !!m_ExtStack.IsEmpty();
415 if (!bEmpty)
416 data->sPathColumnText.Format(IDS_PROGRS_PATHATREV, (LPCTSTR)m_ExtStack.RemoveHead(), rev);
417 else
418 data->sPathColumnText.Format(IDS_PROGRS_ATREV, rev);
420 if ((m_nConflicts>0)&&(bEmpty))
422 // We're going to add another aux item - let's shove this current onto the list first
423 // I don't really like this, but it will do for the moment.
424 m_arData.push_back(data);
425 AddItemToList();
427 data = new NotificationData();
428 data->bAuxItem = true;
429 data->sActionColumnText.LoadString(IDS_PROGRS_CONFLICTSOCCURED_WARNING);
430 data->sPathColumnText.LoadString(IDS_PROGRS_CONFLICTSOCCURED);
431 data->color = m_Colors.GetColor(CColors::Conflict);
432 CSoundUtils::PlayTSVNWarning();
433 // This item will now be added after the switch statement
435 if (!m_basePath.IsEmpty())
436 m_FinishedRevMap[m_basePath.GetSVNApiPath(pool)] = rev;
437 m_RevisionEnd = rev;
438 m_bFinishedItemAdded = true;
440 break;
441 case svn_wc_notify_commit_postfix_txdelta:
442 data->sActionColumnText.LoadString(IDS_SVNACTION_POSTFIX);
443 break;
444 case svn_wc_notify_failed_revert:
445 data->sActionColumnText.LoadString(IDS_SVNACTION_FAILEDREVERT);
446 break;
447 case svn_wc_notify_status_completed:
448 case svn_wc_notify_status_external:
449 data->sActionColumnText.LoadString(IDS_SVNACTION_STATUS);
450 break;
451 case svn_wc_notify_skip:
452 if ((content_state == svn_wc_notify_state_missing)||(content_state == svn_wc_notify_state_obstructed)||(content_state == svn_wc_notify_state_conflicted))
454 data->sActionColumnText.LoadString(IDS_SVNACTION_SKIPMISSING);
456 // The color settings dialog describes the red color with
457 // "possible or real conflict / obstructed" which also applies to
458 // skipped targets during a merge. So we just use the same color.
459 data->color = m_Colors.GetColor(CColors::Conflict);
461 else
462 data->sActionColumnText.LoadString(IDS_SVNACTION_SKIP);
463 break;
464 case svn_wc_notify_locked:
465 if ((lock)&&(lock->owner))
466 data->sActionColumnText.Format(IDS_SVNACTION_LOCKEDBY, (LPCTSTR)CUnicodeUtils::GetUnicode(lock->owner));
467 break;
468 case svn_wc_notify_unlocked:
469 data->sActionColumnText.LoadString(IDS_SVNACTION_UNLOCKED);
470 break;
471 case svn_wc_notify_failed_lock:
472 data->sActionColumnText.LoadString(IDS_SVNACTION_FAILEDLOCK);
473 m_arData.push_back(data);
474 AddItemToList();
475 ReportError(SVN::GetErrorString(err));
476 bDoAddData = false;
477 if (err->apr_err == SVN_ERR_FS_OUT_OF_DATE)
478 m_bLockWarning = true;
479 if (err->apr_err == SVN_ERR_FS_PATH_ALREADY_LOCKED)
480 m_bLockExists = true;
481 break;
482 case svn_wc_notify_failed_unlock:
483 data->sActionColumnText.LoadString(IDS_SVNACTION_FAILEDUNLOCK);
484 m_arData.push_back(data);
485 AddItemToList();
486 ReportError(SVN::GetErrorString(err));
487 bDoAddData = false;
488 if (err->apr_err == SVN_ERR_FS_OUT_OF_DATE)
489 m_bLockWarning = true;
490 break;
491 case svn_wc_notify_changelist_set:
492 data->sActionColumnText.Format(IDS_SVNACTION_CHANGELISTSET, (LPCTSTR)data->changelistname);
493 break;
494 case svn_wc_notify_changelist_clear:
495 data->sActionColumnText.LoadString(IDS_SVNACTION_CHANGELISTCLEAR);
496 break;
497 case svn_wc_notify_changelist_moved:
498 data->sActionColumnText.Format(IDS_SVNACTION_CHANGELISTMOVED, (LPCTSTR)data->changelistname);
499 break;
500 case svn_wc_notify_foreign_merge_begin:
501 case svn_wc_notify_merge_begin:
502 if (range == NULL)
503 data->sActionColumnText.LoadString(IDS_SVNACTION_MERGEBEGINNONE);
504 else if ((data->merge_range.start == data->merge_range.end) || (data->merge_range.start == data->merge_range.end - 1))
505 data->sActionColumnText.Format(IDS_SVNACTION_MERGEBEGINSINGLE, data->merge_range.end);
506 else if (data->merge_range.start - 1 == data->merge_range.end)
507 data->sActionColumnText.Format(IDS_SVNACTION_MERGEBEGINSINGLEREVERSE, data->merge_range.start);
508 else if (data->merge_range.start < data->merge_range.end)
509 data->sActionColumnText.Format(IDS_SVNACTION_MERGEBEGINMULTIPLE, data->merge_range.start + 1, data->merge_range.end);
510 else
511 data->sActionColumnText.Format(IDS_SVNACTION_MERGEBEGINMULTIPLEREVERSE, data->merge_range.start, data->merge_range.end + 1);
512 data->bAuxItem = true;
513 break;
514 #endif
515 default:
516 break;
517 } // switch (data->action)
519 if (bNoNotify)
520 delete data;
521 else
523 if (bDoAddData)
525 m_arData.push_back(data);
526 AddItemToList();
527 if ((!data->bAuxItem) && (m_itemCount > 0))
529 if (m_pProgControl)
531 m_pProgControl->ShowWindow(SW_SHOW);
532 m_pProgControl->SetPos(m_itemCount);
533 m_pProgControl->SetRange32(0, m_itemCountTotal);
535 if (m_pTaskbarList && m_pPostWnd)
537 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_NORMAL);
538 m_pTaskbarList->SetProgressValue(m_pPostWnd->GetSafeHwnd(), m_itemCount, m_itemCountTotal);
542 //if ((action == svn_wc_notify_commit_postfix_txdelta)&&(bSecondResized == FALSE))
544 // ResizeColumns();
545 // bSecondResized = TRUE;
549 return TRUE;
553 CString CGitProgressList::BuildInfoString()
555 CString infotext;
556 if(this->m_Command == GitProgress_Resolve)
557 infotext = _T("You need commit your change after resolve conflict");
558 #if 0
560 CString temp;
561 int added = 0;
562 int copied = 0;
563 int deleted = 0;
564 int restored = 0;
565 int reverted = 0;
566 int resolved = 0;
567 int conflicted = 0;
568 int updated = 0;
569 int merged = 0;
570 int modified = 0;
571 int skipped = 0;
572 int replaced = 0;
574 for (size_t i=0; i<m_arData.size(); ++i)
576 const NotificationData * dat = m_arData[i];
577 switch (dat->action)
579 case svn_wc_notify_add:
580 case svn_wc_notify_update_add:
581 case svn_wc_notify_commit_added:
582 if (dat->bConflictedActionItem)
583 ++conflicted;
584 else
585 ++added;
586 break;
587 case svn_wc_notify_copy:
588 ++copied;
589 break;
590 case svn_wc_notify_delete:
591 case svn_wc_notify_update_delete:
592 case svn_wc_notify_commit_deleted:
593 ++deleted;
594 break;
595 case svn_wc_notify_restore:
596 ++restored;
597 break;
598 case svn_wc_notify_revert:
599 ++reverted;
600 break;
601 case svn_wc_notify_resolved:
602 ++resolved;
603 break;
604 case svn_wc_notify_update_update:
605 if (dat->bConflictedActionItem)
606 ++conflicted;
607 else if ((dat->content_state == svn_wc_notify_state_merged) || (dat->prop_state == svn_wc_notify_state_merged))
608 ++merged;
609 else
610 ++updated;
611 break;
612 case svn_wc_notify_commit_modified:
613 ++modified;
614 break;
615 case svn_wc_notify_skip:
616 ++skipped;
617 break;
618 case svn_wc_notify_commit_replaced:
619 ++replaced;
620 break;
623 if (conflicted)
625 temp.LoadString(IDS_SVNACTION_CONFLICTED);
626 infotext += temp;
627 temp.Format(_T(":%d "), conflicted);
628 infotext += temp;
630 if (skipped)
632 temp.LoadString(IDS_SVNACTION_SKIP);
633 infotext += temp;
634 infotext.AppendFormat(_T(":%d "), skipped);
636 if (merged)
638 temp.LoadString(IDS_SVNACTION_MERGED);
639 infotext += temp;
640 infotext.AppendFormat(_T(":%d "), merged);
642 if (added)
644 temp.LoadString(IDS_SVNACTION_ADD);
645 infotext += temp;
646 infotext.AppendFormat(_T(":%d "), added);
648 if (deleted)
650 temp.LoadString(IDS_SVNACTION_DELETE);
651 infotext += temp;
652 infotext.AppendFormat(_T(":%d "), deleted);
654 if (modified)
656 temp.LoadString(IDS_SVNACTION_MODIFIED);
657 infotext += temp;
658 infotext.AppendFormat(_T(":%d "), modified);
660 if (copied)
662 temp.LoadString(IDS_SVNACTION_COPY);
663 infotext += temp;
664 infotext.AppendFormat(_T(":%d "), copied);
666 if (replaced)
668 temp.LoadString(IDS_SVNACTION_REPLACED);
669 infotext += temp;
670 infotext.AppendFormat(_T(":%d "), replaced);
672 if (updated)
674 temp.LoadString(IDS_SVNACTION_UPDATE);
675 infotext += temp;
676 infotext.AppendFormat(_T(":%d "), updated);
678 if (restored)
680 temp.LoadString(IDS_SVNACTION_RESTORE);
681 infotext += temp;
682 infotext.AppendFormat(_T(":%d "), restored);
684 if (reverted)
686 temp.LoadString(IDS_SVNACTION_REVERT);
687 infotext += temp;
688 infotext.AppendFormat(_T(":%d "), reverted);
690 if (resolved)
692 temp.LoadString(IDS_SVNACTION_RESOLVE);
693 infotext += temp;
694 infotext.AppendFormat(_T(":%d "), resolved);
696 #endif
697 return infotext;
700 void CGitProgressList::SetSelectedList(const CTGitPathList& selPaths)
702 m_selectedPaths = selPaths;
705 void CGitProgressList::ResizeColumns()
707 SetRedraw(FALSE);
709 TCHAR textbuf[MAX_PATH];
711 int maxcol = ((CHeaderCtrl*)(GetDlgItem(0)))->GetItemCount()-1;
712 for (int col = 0; col <= maxcol; ++col)
714 // find the longest width of all items
715 int count = GetItemCount();
716 HDITEM hdi = {0};
717 hdi.mask = HDI_TEXT;
718 hdi.pszText = textbuf;
719 hdi.cchTextMax = sizeof(textbuf);
720 ((CHeaderCtrl*)(GetDlgItem(0)))->GetItem(col, &hdi);
721 int cx = GetStringWidth(hdi.pszText)+20; // 20 pixels for col separator and margin
723 for (int index = 0; index<count; ++index)
725 // get the width of the string and add 12 pixels for the column separator and margins
726 int linewidth = cx;
727 switch (col)
729 case 0:
730 linewidth = GetStringWidth(m_arData[index]->sActionColumnText) + 12;
731 break;
732 case 1:
733 linewidth = GetStringWidth(m_arData[index]->sPathColumnText) + 12;
734 break;
736 if (cx < linewidth)
737 cx = linewidth;
739 SetColumnWidth(col, cx);
742 SetRedraw(TRUE);
745 bool CGitProgressList::SetBackgroundImage(UINT nID)
747 return CAppUtils::SetListCtrlBackgroundImage(GetSafeHwnd(), nID);
750 void CGitProgressList::ReportGitError()
752 ReportError(CGit::GetLibGit2LastErr());
755 void CGitProgressList::ReportUserCanceled()
757 ReportError(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED)));
760 void CGitProgressList::ReportError(const CString& sError)
762 CSoundUtils::PlayTGitError();
763 ReportString(sError, CString(MAKEINTRESOURCE(IDS_ERR_ERROR)), m_Colors.GetColor(CColors::Conflict));
764 m_bErrorsOccurred = true;
767 void CGitProgressList::ReportWarning(const CString& sWarning)
769 CSoundUtils::PlayTGitWarning();
770 ReportString(sWarning, CString(MAKEINTRESOURCE(IDS_WARN_WARNING)), m_Colors.GetColor(CColors::Conflict));
773 void CGitProgressList::ReportNotification(const CString& sNotification)
775 CSoundUtils::PlayTGitNotification();
776 ReportString(sNotification, CString(MAKEINTRESOURCE(IDS_WARN_NOTE)));
779 void CGitProgressList::ReportCmd(const CString& sCmd)
781 ReportString(sCmd, CString(MAKEINTRESOURCE(IDS_PROGRS_CMDINFO)), m_Colors.GetColor(CColors::Cmd));
784 void CGitProgressList::ReportString(CString sMessage, const CString& sMsgKind, COLORREF color)
786 // instead of showing a dialog box with the error message or notification,
787 // just insert the error text into the list control.
788 // that way the user isn't 'interrupted' by a dialog box popping up!
790 // the message may be split up into different lines
791 // so add a new entry for each line of the message
792 while (!sMessage.IsEmpty())
794 NotificationData * data = new NotificationData();
795 data->bAuxItem = true;
796 data->sActionColumnText = sMsgKind;
797 if (sMessage.Find('\n')>=0)
798 data->sPathColumnText = sMessage.Left(sMessage.Find('\n'));
799 else
800 data->sPathColumnText = sMessage;
801 data->sPathColumnText.Trim(_T("\n\r"));
802 data->color = color;
803 if (sMessage.Find('\n')>=0)
805 sMessage = sMessage.Mid(sMessage.Find('\n'));
806 sMessage.Trim(_T("\n\r"));
808 else
809 sMessage.Empty();
810 m_arData.push_back(data);
811 AddItemToList();
815 UINT CGitProgressList::ProgressThreadEntry(LPVOID pVoid)
817 return ((CGitProgressList*)pVoid)->ProgressThread();
820 UINT CGitProgressList::ProgressThread()
822 // The SetParams function should have loaded something for us
824 CString temp;
825 CString sWindowTitle;
826 bool localoperation = false;
827 bool bSuccess = false;
828 m_AlwaysConflicted = false;
830 if(m_pPostWnd)
831 m_pPostWnd->PostMessage(WM_PROG_CMD_START, m_Command);
833 if(m_pProgressLabelCtrl)
835 m_pProgressLabelCtrl->ShowWindow(SW_SHOW);
836 m_pProgressLabelCtrl->SetWindowText(_T(""));
839 // SetAndClearProgressInfo(m_hWnd);
840 m_itemCount = m_itemCountTotal;
842 InterlockedExchange(&m_bThreadRunning, TRUE);
843 iFirstResized = 0;
844 bSecondResized = FALSE;
845 m_bFinishedItemAdded = false;
846 DWORD startTime = GetCurrentTime();
848 if (m_pTaskbarList && m_pPostWnd)
849 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_INDETERMINATE);
851 switch (m_Command)
853 case GitProgress_Add:
854 bSuccess = CmdAdd(sWindowTitle, localoperation);
855 break;
856 case GitProgress_Copy:
857 bSuccess = CmdCopy(sWindowTitle, localoperation);
858 break;
859 case GitProgress_Export:
860 bSuccess = CmdExport(sWindowTitle, localoperation);
861 break;
862 case GitProgress_Rename:
863 bSuccess = CmdRename(sWindowTitle, localoperation);
864 break;
865 case GitProgress_Resolve:
866 bSuccess = CmdResolve(sWindowTitle, localoperation);
867 break;
868 case GitProgress_Revert:
869 bSuccess = CmdRevert(sWindowTitle, localoperation);
870 break;
871 case GitProgress_Switch:
872 bSuccess = CmdSwitch(sWindowTitle, localoperation);
873 break;
874 case GitProgress_SendMail:
875 bSuccess = CmdSendMail(sWindowTitle, localoperation);
876 break;
877 case GitProgress_Clone:
878 bSuccess = CmdClone(sWindowTitle, localoperation);
879 break;
880 case GitProgress_Fetch:
881 bSuccess = CmdFetch(sWindowTitle, localoperation);
882 break;
884 if (!bSuccess)
885 temp.LoadString(IDS_PROGRS_TITLEFAILED);
886 else
887 temp.LoadString(IDS_PROGRS_TITLEFIN);
888 sWindowTitle = sWindowTitle + _T(" ") + temp;
889 if (m_bSetTitle && m_pPostWnd)
890 ::SetWindowText(m_pPostWnd->GetSafeHwnd(), sWindowTitle);
892 KillTimer(TRANSFERTIMER);
893 KillTimer(VISIBLETIMER);
895 if (m_pTaskbarList && m_pPostWnd)
897 if (DidErrorsOccur())
899 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_ERROR);
900 m_pTaskbarList->SetProgressValue(m_pPostWnd->GetSafeHwnd(), 100, 100);
902 else
903 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_NOPROGRESS);
906 CString info = BuildInfoString();
907 if (!bSuccess)
908 info.LoadString(IDS_PROGRS_INFOFAILED);
909 if (m_pInfoCtrl)
910 m_pInfoCtrl->SetWindowText(info);
912 ResizeColumns();
914 DWORD time = GetCurrentTime() - startTime;
916 CString sFinalInfo;
917 if (!m_sTotalBytesTransferred.IsEmpty())
919 temp.Format(IDS_PROGRS_TIME, (time / 1000) / 60, (time / 1000) % 60);
920 sFinalInfo.Format(IDS_PROGRS_FINALINFO, m_sTotalBytesTransferred, (LPCTSTR)temp);
921 if (m_pProgressLabelCtrl)
922 m_pProgressLabelCtrl->SetWindowText(sFinalInfo);
924 else
926 if (m_pProgressLabelCtrl)
927 m_pProgressLabelCtrl->ShowWindow(SW_HIDE);
930 if (m_pProgControl)
931 m_pProgControl->ShowWindow(SW_HIDE);
933 if (!m_bFinishedItemAdded)
935 CString log, str;
936 if (bSuccess)
937 str.LoadString(IDS_SUCCESS);
938 else
939 str.LoadString(IDS_FAIL);
940 log.Format(_T("%s (%d ms @ %s)"), str, time, CLoglistUtils::FormatDateAndTime(CTime::GetCurrentTime(), DATE_SHORTDATE, true, false));
942 // there's no "finished: xxx" line at the end. We add one here to make
943 // sure the user sees that the command is actually finished.
944 ReportString(log, CString(MAKEINTRESOURCE(IDS_PROGRS_FINISHED)), bSuccess? RGB(0,0,255) : RGB(255,0,0));
947 int count = GetItemCount();
948 if ((count > 0)&&(m_bLastVisible))
949 EnsureVisible(count-1, FALSE);
951 CLogFile logfile;
952 if (logfile.Open())
954 logfile.AddTimeLine();
955 for (size_t i = 0; i < m_arData.size(); ++i)
957 NotificationData * data = m_arData[i];
958 temp.Format(_T("%-20s : %s"), (LPCTSTR)data->sActionColumnText, (LPCTSTR)data->sPathColumnText);
959 logfile.AddLine(temp);
961 if (!sFinalInfo.IsEmpty())
962 logfile.AddLine(sFinalInfo);
963 logfile.Close();
966 m_bCancelled = TRUE;
967 InterlockedExchange(&m_bThreadRunning, FALSE);
968 #if 0 //need
969 RefreshCursor();
970 #endif
972 if (m_pPostWnd)
973 m_pPostWnd->PostMessage(WM_PROG_CMD_FINISH, this->m_Command, 0L);
975 #if 0
976 DWORD dwAutoClose = CRegStdDWORD(_T("Software\\TortoiseGit\\AutoClose"), CLOSE_MANUAL);
977 if (m_options & ProgOptDryRun)
978 dwAutoClose = 0; // dry run means progress dialog doesn't auto close at all
979 if (!m_bLastVisible)
980 dwAutoClose = 0;
981 if (m_dwCloseOnEnd != (DWORD)-1)
982 dwAutoClose = m_dwCloseOnEnd; // command line value has priority over setting value
983 if ((dwAutoClose == CLOSE_NOERRORS)&&(!m_bErrorsOccurred))
984 PostMessage(WM_COMMAND, 1, (LPARAM)GetDlgItem(IDOK)->m_hWnd);
985 if ((dwAutoClose == CLOSE_NOCONFLICTS)&&(!m_bErrorsOccurred)&&(m_nConflicts==0))
986 PostMessage(WM_COMMAND, 1, (LPARAM)GetDlgItem(IDOK)->m_hWnd);
987 if ((dwAutoClose == CLOSE_NOMERGES)&&(!m_bErrorsOccurred)&&(m_nConflicts==0)&&(!m_bMergesAddsDeletesOccurred))
988 PostMessage(WM_COMMAND, 1, (LPARAM)GetDlgItem(IDOK)->m_hWnd);
989 if ((dwAutoClose == CLOSE_LOCAL)&&(!m_bErrorsOccurred)&&(m_nConflicts==0)&&(localoperation))
990 PostMessage(WM_COMMAND, 1, (LPARAM)GetDlgItem(IDOK)->m_hWnd);
991 #endif
993 //Don't do anything here which might cause messages to be sent to the window
994 //The window thread is probably now blocked in OnOK if we've done an auto close
995 return 0;
998 void CGitProgressList::OnLvnGetdispinfoSvnprogress(NMHDR *pNMHDR, LRESULT *pResult)
1000 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1002 if (pDispInfo)
1004 if (pDispInfo->item.mask & LVIF_TEXT)
1006 if (pDispInfo->item.iItem < (int)m_arData.size())
1008 const NotificationData * data = m_arData[pDispInfo->item.iItem];
1009 switch (pDispInfo->item.iSubItem)
1011 case 0:
1012 lstrcpyn(m_columnbuf, data->sActionColumnText, MAX_PATH);
1013 break;
1014 case 1:
1015 lstrcpyn(m_columnbuf, data->sPathColumnText, pDispInfo->item.cchTextMax);
1016 if (!data->bAuxItem)
1018 int cWidth = GetColumnWidth(1);
1019 cWidth = max(12, cWidth-12);
1020 CDC * pDC = GetDC();
1021 if (pDC != NULL)
1023 CFont * pFont = pDC->SelectObject(GetFont());
1024 PathCompactPath(pDC->GetSafeHdc(), m_columnbuf, cWidth);
1025 pDC->SelectObject(pFont);
1026 ReleaseDC(pDC);
1029 break;
1030 default:
1031 m_columnbuf[0] = 0;
1033 pDispInfo->item.pszText = m_columnbuf;
1037 *pResult = 0;
1040 void CGitProgressList::OnNMCustomdrawSvnprogress(NMHDR *pNMHDR, LRESULT *pResult)
1042 NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );
1044 // Take the default processing unless we set this to something else below.
1045 *pResult = CDRF_DODEFAULT;
1047 // First thing - check the draw stage. If it's the control's prepaint
1048 // stage, then tell Windows we want messages for every item.
1050 if ( CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage )
1052 *pResult = CDRF_NOTIFYITEMDRAW;
1054 else if ( CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage )
1056 // This is the prepaint stage for an item. Here's where we set the
1057 // item's text color. Our return value will tell Windows to draw the
1058 // item itself, but it will use the new color we set here.
1060 // Tell Windows to paint the control itself.
1061 *pResult = CDRF_DODEFAULT;
1063 ASSERT(pLVCD->nmcd.dwItemSpec < m_arData.size());
1064 if(pLVCD->nmcd.dwItemSpec >= m_arData.size())
1066 return;
1068 const NotificationData * data = m_arData[pLVCD->nmcd.dwItemSpec];
1069 ASSERT(data != NULL);
1070 if (data == NULL)
1071 return;
1073 // Store the color back in the NMLVCUSTOMDRAW struct.
1074 pLVCD->clrText = data->color;
1078 void CGitProgressList::OnNMDblclkSvnprogress(NMHDR * /*pNMHDR*/, LRESULT * /*pResult*/)
1080 #if 0
1081 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1082 *pResult = 0;
1083 if (pNMLV->iItem < 0)
1084 return;
1085 if (m_options & ProgOptDryRun)
1086 return; //don't do anything in a dry-run.
1088 const NotificationData * data = m_arData[pNMLV->iItem];
1089 if (data == NULL)
1090 return;
1092 if (data->bConflictedActionItem)
1094 // We've double-clicked on a conflicted item - do a three-way merge on it
1095 SVNDiff::StartConflictEditor(data->path);
1097 else if ((data->action == svn_wc_notify_update_update) && ((data->content_state == svn_wc_notify_state_merged)||(GitProgress_Merge == m_Command)) || (data->action == svn_wc_notify_resolved))
1099 // This is a modified file which has been merged on update. Diff it against base
1100 CTGitPath temporaryFile;
1101 SVNDiff diff(this, this->m_hWnd, true);
1102 diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1103 svn_revnum_t baseRev = 0;
1104 diff.DiffFileAgainstBase(data->path, baseRev);
1106 else if ((!data->bAuxItem)&&(data->path.Exists())&&(!data->path.IsDirectory()))
1108 bool bOpenWith = false;
1109 int ret = (int)ShellExecute(m_hWnd, NULL, data->path.GetWinPath(), NULL, NULL, SW_SHOWNORMAL);
1110 if (ret <= HINSTANCE_ERROR)
1111 bOpenWith = true;
1112 if (bOpenWith)
1114 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1115 cmd += data->path.GetWinPathString() + _T(" ");
1116 CAppUtils::LaunchApplication(cmd, NULL, false);
1119 #endif
1122 void CGitProgressList::OnHdnItemclickSvnprogress(NMHDR *pNMHDR, LRESULT *pResult)
1124 LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
1125 if (m_bThreadRunning)
1126 return;
1127 if (m_nSortedColumn == phdr->iItem)
1128 m_bAscending = !m_bAscending;
1129 else
1130 m_bAscending = TRUE;
1131 m_nSortedColumn = phdr->iItem;
1132 Sort();
1134 CString temp;
1135 SetRedraw(FALSE);
1136 DeleteAllItems();
1137 SetItemCountEx (static_cast<int>(m_arData.size()));
1139 SetRedraw(TRUE);
1141 *pResult = 0;
1144 bool CGitProgressList::NotificationDataIsAux(const NotificationData* pData)
1146 return pData->bAuxItem;
1148 BOOL CGitProgressList::Notify(const git_wc_notify_action_t action, CString str, const git_oid *a, const git_oid *b)
1150 NotificationData * data = new NotificationData();
1151 data->action = action;
1152 data->bAuxItem = false;
1154 if (action == git_wc_notify_update_ref)
1156 data->m_NewHash = b->id;
1157 data->m_OldHash = a->id;
1158 data->sActionColumnText.LoadString(IDS_GITACTION_UPDATE_REF);
1159 data->sPathColumnText.Format(_T("%s\t %s -> %s"), str,
1160 data->m_OldHash.ToString().Left(g_Git.GetShortHASHLength()),
1161 data->m_NewHash.ToString().Left(g_Git.GetShortHASHLength()));
1165 m_arData.push_back(data);
1166 AddItemToList();
1168 if (m_pAnimate)
1169 m_pAnimate->Stop();
1170 if (m_pAnimate)
1171 m_pAnimate->ShowWindow(SW_HIDE);
1172 return TRUE;
1174 BOOL CGitProgressList::Notify(const git_wc_notify_action_t /*action*/, const git_transfer_progress *stat)
1176 static unsigned int start = 0;
1177 unsigned int dt = GetCurrentTime() - start;
1178 size_t ds;
1179 double speed = 0;
1181 if (m_bCancelled)
1182 return FALSE;
1184 if (dt > 100)
1186 start = GetCurrentTime();
1187 ds = stat->received_bytes - m_TotalBytesTransferred;
1188 speed = ds * 1000.0/dt;
1189 m_TotalBytesTransferred = stat->received_bytes;
1191 else
1193 return TRUE;
1196 int progress;
1197 progress = stat->received_objects + stat->indexed_objects;
1199 if ((stat->total_objects > 1000) && m_pProgControl && (!m_pProgControl->IsWindowVisible()))
1201 if (m_pProgControl)
1202 m_pProgControl->ShowWindow(SW_SHOW);
1205 if (m_pProgressLabelCtrl && m_pProgressLabelCtrl->IsWindowVisible())
1206 m_pProgressLabelCtrl->ShowWindow(SW_SHOW);
1208 if (m_pProgControl)
1210 m_pProgControl->SetPos(progress);
1211 m_pProgControl->SetRange32(0, 2 * stat->total_objects);
1213 if (m_pTaskbarList && m_pPostWnd)
1215 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_NORMAL);
1216 m_pTaskbarList->SetProgressValue(m_pPostWnd->GetSafeHwnd(), progress, 2 * stat->total_objects);
1219 CString progText;
1220 if (stat->received_bytes < 1024)
1221 m_sTotalBytesTransferred.Format(IDS_SVN_PROGRESS_TOTALBYTESTRANSFERRED, (int64_t)stat->received_bytes);
1222 else if (stat->received_bytes < 1200000)
1223 m_sTotalBytesTransferred.Format(IDS_SVN_PROGRESS_TOTALTRANSFERRED, (int64_t)stat->received_bytes / 1024);
1224 else
1225 m_sTotalBytesTransferred.Format(IDS_SVN_PROGRESS_TOTALMBTRANSFERRED, (double)((double)stat->received_bytes / 1024000.0));
1227 CString str;
1228 if(speed < 1024)
1229 str.Format(_T("%fB/s"), speed);
1230 else if(speed < 1024 * 1024)
1231 str.Format(_T("%.2fKB/s"), speed / 1024);
1232 else
1233 str.Format(_T("%.2fMB/s"), speed / 1024000.0);
1235 progText.Format(IDS_SVN_PROGRESS_TOTALANDSPEED, (LPCTSTR)m_sTotalBytesTransferred, (LPCTSTR)str);
1236 if (m_pProgressLabelCtrl)
1237 m_pProgressLabelCtrl->SetWindowText(progText);
1239 return TRUE;
1242 void CGitProgressList::OnTimer(UINT_PTR nIDEvent)
1244 if (nIDEvent == TRANSFERTIMER)
1246 CString progText;
1247 CString progSpeed;
1248 progSpeed.Format(IDS_SVN_PROGRESS_BYTES_SEC, 0);
1249 progText.Format(IDS_SVN_PROGRESS_TOTALANDSPEED, (LPCTSTR)m_sTotalBytesTransferred, (LPCTSTR)progSpeed);
1250 if (m_pProgressLabelCtrl)
1251 m_pProgressLabelCtrl->SetWindowText(progText);
1253 KillTimer(TRANSFERTIMER);
1255 if (nIDEvent == VISIBLETIMER)
1257 if (nEnsureVisibleCount)
1258 EnsureVisible(GetItemCount()-1, false);
1259 nEnsureVisibleCount = 0;
1263 void CGitProgressList::Sort()
1265 if(m_arData.size() < 2)
1267 return;
1270 // We need to sort the blocks which lie between the auxiliary entries
1271 // This is so that any aux data stays where it was
1272 NotificationDataVect::iterator actionBlockBegin;
1273 NotificationDataVect::iterator actionBlockEnd = m_arData.begin(); // We start searching from here
1275 for(;;)
1277 // Search to the start of the non-aux entry in the next block
1278 actionBlockBegin = std::find_if(actionBlockEnd, m_arData.end(), std::not1(std::ptr_fun(&CGitProgressList::NotificationDataIsAux)));
1279 if(actionBlockBegin == m_arData.end())
1281 // There are no more actions
1282 break;
1284 // Now search to find the end of the block
1285 actionBlockEnd = std::find_if(actionBlockBegin+1, m_arData.end(), std::ptr_fun(&CGitProgressList::NotificationDataIsAux));
1286 // Now sort the block
1287 std::sort(actionBlockBegin, actionBlockEnd, &CGitProgressList::SortCompare);
1291 bool CGitProgressList::SortCompare(const NotificationData * pData1, const NotificationData * pData2)
1293 int result = 0;
1294 switch (m_nSortedColumn)
1296 case 0: //action column
1297 result = pData1->sActionColumnText.Compare(pData2->sActionColumnText);
1298 break;
1299 case 1: //path column
1300 // Compare happens after switch()
1301 break;
1302 default:
1303 break;
1306 // Sort by path if everything else is equal
1307 if (result == 0)
1309 result = CTGitPath::Compare(pData1->path, pData2->path);
1312 if (!m_bAscending)
1313 result = -result;
1314 return result < 0;
1317 void CGitProgressList::OnContextMenu(CWnd* pWnd, CPoint point)
1319 if (m_options & ProgOptDryRun)
1320 return; // don't do anything in a dry-run.
1322 if (pWnd == this)
1324 int selIndex = GetSelectionMark();
1325 if ((point.x == -1) && (point.y == -1))
1327 // Menu was invoked from the keyboard rather than by right-clicking
1328 CRect rect;
1329 GetItemRect(selIndex, &rect, LVIR_LABEL);
1330 ClientToScreen(&rect);
1331 point = rect.CenterPoint();
1334 if ((selIndex >= 0)&&(!m_bThreadRunning))
1336 // entry is selected, thread has finished with updating so show the popup menu
1337 CIconMenu popup;
1338 if (popup.CreatePopupMenu())
1340 bool bAdded = false;
1341 NotificationData * data = m_arData[selIndex];
1342 if ((data)&&(!data->path.IsDirectory()))
1345 if (data->action == svn_wc_notify_update_update || data->action == svn_wc_notify_resolved)
1347 if (GetSelectedCount() == 1)
1349 popup.AppendMenuIcon(ID_COMPARE, IDS_LOG_POPUP_COMPARE, IDI_DIFF);
1350 bAdded = true;
1354 if (data->bConflictedActionItem)
1356 if (GetSelectedCount() == 1)
1358 popup.AppendMenuIcon(ID_EDITCONFLICT, IDS_MENUCONFLICT,IDI_CONFLICT);
1359 popup.SetDefaultItem(ID_EDITCONFLICT, FALSE);
1360 popup.AppendMenuIcon(ID_CONFLICTRESOLVE, IDS_SVNPROGRESS_MENUMARKASRESOLVED,IDI_RESOLVE);
1362 popup.AppendMenuIcon(ID_CONFLICTUSETHEIRS, IDS_SVNPROGRESS_MENUUSETHEIRS,IDI_RESOLVE);
1363 popup.AppendMenuIcon(ID_CONFLICTUSEMINE, IDS_SVNPROGRESS_MENUUSEMINE,IDI_RESOLVE);
1365 else if ((data->content_state == svn_wc_notify_state_merged)||(GitProgress_Merge == m_Command)||(data->action == svn_wc_notify_resolved))
1366 popup.SetDefaultItem(ID_COMPARE, FALSE);
1368 if (GetSelectedCount() == 1)
1370 if ((data->action == git_wc_notify_add)||
1371 (data->action == git_wc_notify_revert)||
1372 (data->action == git_wc_notify_resolved)||
1373 (data->action == git_wc_notify_checkout)||
1374 (data->action == git_wc_notify_update_ref))
1376 popup.AppendMenuIcon(ID_LOG, IDS_MENULOG,IDI_LOG);
1377 popup.AppendMenu(MF_SEPARATOR, NULL);
1378 popup.AppendMenuIcon(ID_OPEN, IDS_LOG_POPUP_OPEN, IDI_OPEN);
1379 popup.AppendMenuIcon(ID_OPENWITH, IDS_LOG_POPUP_OPENWITH, IDI_OPEN);
1380 bAdded = true;
1383 } // if ((data)&&(!data->path.IsDirectory()))
1384 if (GetSelectedCount() == 1)
1386 if (data)
1388 CString sPath = GetPathFromColumnText(data->sPathColumnText);
1389 CTGitPath path = CTGitPath(sPath);
1390 if (!sPath.IsEmpty())
1392 if (path.GetDirectory().Exists())
1394 popup.AppendMenuIcon(ID_EXPLORE, IDS_SVNPROGRESS_MENUOPENPARENT, IDI_EXPLORER);
1395 bAdded = true;
1400 if (GetSelectedCount() > 0)
1402 if (bAdded)
1403 popup.AppendMenu(MF_SEPARATOR, NULL);
1404 popup.AppendMenuIcon(ID_COPY, IDS_LOG_POPUP_COPYTOCLIPBOARD,IDI_COPYCLIP);
1405 bAdded = true;
1407 if (bAdded)
1409 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
1410 #if 0//need
1411 DialogEnableWindow(IDOK, FALSE);
1412 #endif
1413 //this->SetPromptApp(&theApp);
1414 theApp.DoWaitCursor(1);
1415 bool bOpenWith = false;
1416 switch (cmd)
1418 case ID_COPY:
1420 CString sLines;
1421 POSITION pos = GetFirstSelectedItemPosition();
1422 while (pos)
1424 int nItem = GetNextSelectedItem(pos);
1425 NotificationData * data = m_arData[nItem];
1426 if (data)
1428 sLines += data->sPathColumnText;
1429 sLines += _T("\r\n");
1432 sLines.TrimRight();
1433 if (!sLines.IsEmpty())
1435 CStringUtils::WriteAsciiStringToClipboard(sLines, GetSafeHwnd());
1438 break;
1439 case ID_EXPLORE:
1441 CString sPath = GetPathFromColumnText(data->sPathColumnText);
1443 CTGitPath path = CTGitPath(sPath);
1444 ShellExecute(m_hWnd, _T("explore"), path.GetDirectory().GetWinPath(), NULL, path.GetDirectory().GetWinPath(), SW_SHOW);
1446 break;
1447 #if 0
1448 case ID_COMPARE:
1450 svn_revnum_t rev = -1;
1451 StringRevMap::iterator it = m_UpdateStartRevMap.end();
1452 if (data->basepath.IsEmpty())
1453 it = m_UpdateStartRevMap.begin();
1454 else
1455 it = m_UpdateStartRevMap.find(data->basepath.GetSVNApiPath(pool));
1456 if (it != m_UpdateStartRevMap.end())
1457 rev = it->second;
1458 // if the file was merged during update, do a three way diff between OLD, MINE, THEIRS
1459 if (data->content_state == svn_wc_notify_state_merged)
1461 CTGitPath basefile = CTempFiles::Instance().GetTempFilePath(false, data->path, rev);
1462 CTGitPath newfile = CTempFiles::Instance().GetTempFilePath(false, data->path, SVNRev::REV_HEAD);
1463 SVN svn;
1464 if (!svn.Cat(data->path, SVNRev(SVNRev::REV_WC), rev, basefile))
1466 CMessageBox::Show(m_hWnd, svn.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1467 DialogEnableWindow(IDOK, TRUE);
1468 break;
1470 // If necessary, convert the line-endings on the file before diffing
1471 if ((DWORD)CRegDWORD(_T("Software\\TortoiseGit\\ConvertBase"), TRUE))
1473 CTGitPath temporaryFile = CTempFiles::Instance().GetTempFilePath(false, data->path, SVNRev::REV_BASE);
1474 if (!svn.Cat(data->path, SVNRev(SVNRev::REV_BASE), SVNRev(SVNRev::REV_BASE), temporaryFile))
1476 temporaryFile.Reset();
1477 break;
1479 else
1481 newfile = temporaryFile;
1485 SetFileAttributes(newfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1486 SetFileAttributes(basefile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1487 CString revname, wcname, basename;
1488 revname.Format(_T("%s Revision %ld"), (LPCTSTR)data->path.GetUIFileOrDirectoryName(), rev);
1489 wcname.Format(IDS_DIFF_WCNAME, (LPCTSTR)data->path.GetUIFileOrDirectoryName());
1490 basename.Format(IDS_DIFF_BASENAME, (LPCTSTR)data->path.GetUIFileOrDirectoryName());
1491 CAppUtils::StartExtMerge(basefile, newfile, data->path, data->path, basename, revname, wcname, CString(), true);
1493 else
1495 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, data->path, rev);
1496 SVN svn;
1497 if (!svn.Cat(data->path, SVNRev(SVNRev::REV_WC), rev, tempfile))
1499 CMessageBox::Show(m_hWnd, svn.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1500 DialogEnableWindow(IDOK, TRUE);
1501 break;
1503 else
1505 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1506 CString revname, wcname;
1507 revname.Format(_T("%s Revision %ld"), (LPCTSTR)data->path.GetUIFileOrDirectoryName(), rev);
1508 wcname.Format(IDS_DIFF_WCNAME, (LPCTSTR)data->path.GetUIFileOrDirectoryName());
1509 CAppUtils::StartExtDiff(
1510 tempfile, data->path, revname, wcname,
1511 CAppUtils::DiffFlags().AlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000)));
1515 break;
1516 case ID_EDITCONFLICT:
1518 CString sPath = GetPathFromColumnText(data->sPathColumnText);
1519 SVNDiff::StartConflictEditor(CTGitPath(sPath));
1521 break;
1522 case ID_CONFLICTUSETHEIRS:
1523 case ID_CONFLICTUSEMINE:
1524 case ID_CONFLICTRESOLVE:
1526 svn_wc_conflict_choice_t result = svn_wc_conflict_choose_merged;
1527 switch (cmd)
1529 case ID_CONFLICTUSETHEIRS:
1530 result = svn_wc_conflict_choose_theirs_full;
1531 break;
1532 case ID_CONFLICTUSEMINE:
1533 result = svn_wc_conflict_choose_mine_full;
1534 break;
1535 case ID_CONFLICTRESOLVE:
1536 result = svn_wc_conflict_choose_merged;
1537 break;
1539 SVN svn;
1540 POSITION pos = GetFirstSelectedItemPosition();
1541 CString sResolvedPaths;
1542 while (pos)
1544 int nItem = GetNextSelectedItem(pos);
1545 NotificationData * data = m_arData[nItem];
1546 if (data)
1548 if (data->bConflictedActionItem)
1550 if (!svn.Resolve(data->path, result, FALSE))
1552 CMessageBox::Show(m_hWnd, svn.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1553 DialogEnableWindow(IDOK, TRUE);
1554 break;
1556 else
1558 data->color = ::GetSysColor(COLOR_WINDOWTEXT);
1559 data->action = svn_wc_notify_resolved;
1560 data->sActionColumnText.LoadString(IDS_SVNACTION_RESOLVE);
1561 data->bConflictedActionItem = false;
1562 m_nConflicts--;
1564 if (m_nConflicts==0)
1566 // When the last conflict is resolved we remove
1567 // the warning which we assume is in the last line.
1568 int nIndex = GetItemCount()-1;
1569 VERIFY(DeleteItem(nIndex));
1571 delete m_arData[nIndex];
1572 m_arData.pop_back();
1574 sResolvedPaths += data->path.GetWinPathString() + _T("\n");
1579 Invalidate();
1580 CString info = BuildInfoString();
1581 SetDlgItemText(IDC_INFOTEXT, info);
1583 if (!sResolvedPaths.IsEmpty())
1585 CString msg;
1586 msg.Format(IDS_SVNPROGRESS_RESOLVED, (LPCTSTR)sResolvedPaths);
1587 CMessageBox::Show(m_hWnd, msg, _T("TortoiseGit"), MB_OK | MB_ICONINFORMATION);
1590 break;
1591 #endif
1592 case ID_LOG:
1594 CString cmd = _T("/command:log");
1595 CString sPath = GetPathFromColumnText(data->sPathColumnText);
1596 if(data->action == git_wc_notify_update_ref)
1598 cmd += _T(" /path:\"") + GetPathFromColumnText(CString()) + _T("\"");
1599 if (!data->m_OldHash.IsEmpty())
1600 cmd += _T(" /startrev:") + data->m_OldHash.ToString();
1601 if (!data->m_NewHash.IsEmpty())
1602 cmd += _T(" /endrev:") + data->m_NewHash.ToString();
1604 else
1605 cmd += _T(" /path:\"") + sPath + _T("\"");
1606 CAppUtils::RunTortoiseGitProc(cmd);
1608 break;
1609 case ID_OPENWITH:
1610 bOpenWith = true;
1611 case ID_OPEN:
1613 int ret = 0;
1614 CString sWinPath = GetPathFromColumnText(data->sPathColumnText);
1615 if (!bOpenWith)
1616 ret = (int)ShellExecute(this->m_hWnd, NULL, (LPCTSTR)sWinPath, NULL, NULL, SW_SHOWNORMAL);
1617 if ((ret <= HINSTANCE_ERROR)||bOpenWith)
1619 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1620 cmd += sWinPath + _T(" ");
1621 CAppUtils::LaunchApplication(cmd, NULL, false);
1625 #if 0 //need
1626 DialogEnableWindow(IDOK, TRUE);
1627 #endif
1628 theApp.DoWaitCursor(-1);
1629 } // if (bAdded)
1635 void CGitProgressList::OnLvnBegindragSvnprogress(NMHDR* , LRESULT *pResult)
1637 //LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1638 #if 0
1639 int selIndex = GetSelectionMark();
1640 if (selIndex < 0)
1641 return;
1643 CDropFiles dropFiles; // class for creating DROPFILES struct
1645 int index;
1646 POSITION pos = GetFirstSelectedItemPosition();
1647 while ( (index = GetNextSelectedItem(pos)) >= 0 )
1649 NotificationData * data = m_arData[index];
1651 if ( data->kind==svn_node_file || data->kind==svn_node_dir )
1653 CString sPath = GetPathFromColumnText(data->sPathColumnText);
1655 dropFiles.AddFile( sPath );
1659 if ( dropFiles.GetCount()>0 )
1661 dropFiles.CreateStructure();
1663 #endif
1664 *pResult = 0;
1667 void CGitProgressList::OnSize(UINT nType, int cx, int cy)
1669 CListCtrl::OnSize(nType, cx, cy);
1670 if ((nType == SIZE_RESTORED)&&(m_bLastVisible))
1672 if(!m_hWnd)
1673 return;
1675 int count = GetItemCount();
1676 if (count > 0)
1677 EnsureVisible(count-1, false);
1681 //////////////////////////////////////////////////////////////////////////
1682 /// commands
1683 //////////////////////////////////////////////////////////////////////////
1684 bool CGitProgressList::CmdAdd(CString& sWindowTitle, bool& localoperation)
1686 localoperation = true;
1687 SetWindowTitle(IDS_PROGRS_TITLE_ADD, g_Git.m_CurrentDir + _T("\\") + m_targetPathList.GetCommonRoot().GetUIPathString(), sWindowTitle);
1688 SetBackgroundImage(IDI_ADD_BKG);
1689 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_ADD)));
1691 if (CRegDWORD(_T("Software\\TortoiseGit\\UseLibgit2"), TRUE) == TRUE)
1693 git_repository *repo = NULL;
1694 git_index *index;
1696 CStringA gitdir = CUnicodeUtils::GetMulti(CTGitPath(g_Git.m_CurrentDir).GetGitPathString(), CP_UTF8);
1697 if (git_repository_open(&repo, gitdir.GetBuffer()))
1699 gitdir.ReleaseBuffer();
1700 ReportGitError();
1701 return false;
1703 gitdir.ReleaseBuffer();
1705 git_config * config;
1706 git_config_new(&config);
1708 CStringA projectConfigA = CUnicodeUtils::GetMulti(g_Git.GetGitLocalConfig(), CP_UTF8);
1709 if (git_config_add_file_ondisk(config, projectConfigA.GetBuffer(), 4, FALSE))
1711 projectConfigA.ReleaseBuffer();
1712 ReportGitError();
1713 git_config_free(config);
1714 git_repository_free(repo);
1715 return false;
1717 projectConfigA.ReleaseBuffer();
1718 CString globalConfig = g_Git.GetGitGlobalConfig();
1719 if (PathFileExists(globalConfig))
1721 CStringA globalConfigA = CUnicodeUtils::GetMulti(globalConfig, CP_UTF8);
1722 if (git_config_add_file_ondisk(config, globalConfigA.GetBuffer(), 3, FALSE))
1724 globalConfigA.ReleaseBuffer();
1725 ReportGitError();
1726 git_config_free(config);
1727 git_repository_free(repo);
1728 return false;
1730 globalConfigA.ReleaseBuffer();
1732 CString globalXDGConfig = g_Git.GetGitGlobalXDGConfig();
1733 if (PathFileExists(globalXDGConfig))
1735 CStringA globalXDGConfigA = CUnicodeUtils::GetMulti(globalXDGConfig, CP_UTF8);
1736 if (git_config_add_file_ondisk(config, globalXDGConfigA.GetBuffer(), 2, FALSE))
1738 globalXDGConfigA.ReleaseBuffer();
1739 ReportGitError();
1740 git_config_free(config);
1741 git_repository_free(repo);
1742 return false;
1744 globalXDGConfigA.ReleaseBuffer();
1746 CString systemConfig = g_Git.GetGitSystemConfig();
1747 if (!systemConfig.IsEmpty())
1749 CStringA systemConfigA = CUnicodeUtils::GetMulti(systemConfig, CP_UTF8);
1750 if (git_config_add_file_ondisk(config, systemConfigA.GetBuffer(), 1, FALSE))
1752 systemConfigA.ReleaseBuffer();
1753 ReportGitError();
1754 git_config_free(config);
1755 git_repository_free(repo);
1756 return false;
1758 systemConfigA.ReleaseBuffer();
1761 git_repository_set_config(repo, config);
1762 git_config_free(config);
1763 config = nullptr;
1765 if (git_repository_index(&index, repo))
1767 ReportGitError();
1768 git_repository_free(repo);
1769 return false;
1771 if (git_index_read(index))
1773 ReportGitError();
1774 git_index_free(index);
1775 git_repository_free(repo);
1776 return false;
1779 m_itemCountTotal = m_targetPathList.GetCount();
1780 for (m_itemCount = 0; m_itemCount < m_itemCountTotal; ++m_itemCount)
1782 if (git_index_add_bypath(index, CStringA(CUnicodeUtils::GetMulti(m_targetPathList[m_itemCount].GetGitPathString(), CP_UTF8)).GetBuffer()))
1784 ReportGitError();
1785 git_index_free(index);
1786 git_repository_free(repo);
1787 return false;
1789 Notify(m_targetPathList[m_itemCount], git_wc_notify_add);
1791 if (IsCancelled() == TRUE)
1793 git_index_free(index);
1794 git_repository_free(repo);
1796 ReportUserCanceled();
1797 return false;
1801 if (git_index_write(index))
1803 ReportGitError();
1804 git_index_free(index);
1805 git_repository_free(repo);
1806 return false;
1809 git_index_free(index);
1810 git_repository_free(repo);
1812 else
1814 CMassiveGitTask mgt(L"add -f");
1815 mgt.ExecuteWithNotify(&m_targetPathList, m_bCancelled, git_wc_notify_add, this);
1818 CShellUpdater::Instance().AddPathsForUpdate(m_targetPathList);
1820 return true;
1823 bool CGitProgressList::CmdCopy(CString& /*sWindowTitle*/, bool& /*localoperation*/)
1825 #if 0
1826 ASSERT(m_targetPathList.GetCount() == 1);
1827 sWindowTitle.LoadString(IDS_PROGRS_TITLE_COPY);
1828 SetWindowText(sWindowTitle); // needs to be updated, see TSVN rev. 21375
1829 SetBackgroundImage(IDI_COPY_BKG);
1831 CString sCmdInfo;
1832 sCmdInfo.Format(IDS_PROGRS_CMD_COPY,
1833 m_targetPathList[0].IsUrl() ? (LPCTSTR)m_targetPathList[0].GetSVNPathString() : m_targetPathList[0].GetWinPath(),
1834 (LPCTSTR)m_url.GetSVNPathString(), (LPCTSTR)m_Revision.ToString());
1835 ReportCmd(sCmdInfo);
1837 if (!Copy(m_targetPathList, m_url, m_Revision, m_pegRev, m_sMessage))
1839 ReportSVNError();
1840 return false;
1842 if (m_options & ProgOptSwitchAfterCopy)
1844 sCmdInfo.Format(IDS_PROGRS_CMD_SWITCH,
1845 m_targetPathList[0].GetWinPath(),
1846 (LPCTSTR)m_url.GetSVNPathString(), (LPCTSTR)m_Revision.ToString());
1847 ReportCmd(sCmdInfo);
1848 if (!Switch(m_targetPathList[0], m_url, SVNRev::REV_HEAD, SVNRev::REV_HEAD, m_depth, TRUE, m_options & ProgOptIgnoreExternals))
1850 if (!Switch(m_targetPathList[0], m_url, SVNRev::REV_HEAD, m_Revision, m_depth, TRUE, m_options & ProgOptIgnoreExternals))
1852 ReportSVNError();
1853 return false;
1857 else
1859 if (SVN::PathIsURL(m_url))
1861 CString sMsg(MAKEINTRESOURCE(IDS_PROGRS_COPY_WARNING));
1862 ReportNotification(sMsg);
1865 #endif
1866 return true;
1869 bool CGitProgressList::CmdExport(CString& /*sWindowTitle*/, bool& /*localoperation*/)
1871 #if 0
1872 ASSERT(m_targetPathList.GetCount() == 1);
1873 sWindowTitle.LoadString(IDS_PROGRS_TITLE_EXPORT);
1874 sWindowTitle = m_url.GetUIFileOrDirectoryName()+_T(" - ")+sWindowTitle;
1875 SetWindowText(sWindowTitle); // needs to be updated, see TSVN rev. 21375
1876 SetBackgroundImage(IDI_EXPORT_BKG);
1877 CString eol;
1878 if (m_options & ProgOptEolCRLF)
1879 eol = _T("CRLF");
1880 if (m_options & ProgOptEolLF)
1881 eol = _T("LF");
1882 if (m_options & ProgOptEolCR)
1883 eol = _T("CR");
1884 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_EXPORT)));
1885 if (!Export(m_url, m_targetPathList[0], m_Revision, m_Revision, TRUE, m_options & ProgOptIgnoreExternals, m_depth, NULL, FALSE, eol))
1887 ReportSVNError();
1888 return false;
1890 #endif
1891 return true;
1894 bool CGitProgressList::CmdRename(CString& /*sWindowTitle*/, bool& /*localoperation*/)
1896 #if 0
1897 ASSERT(m_targetPathList.GetCount() == 1);
1898 if ((!m_targetPathList[0].IsUrl())&&(!m_url.IsUrl()))
1899 localoperation = true;
1900 sWindowTitle.LoadString(IDS_PROGRS_TITLE_RENAME);
1901 SetWindowText(sWindowTitle); // needs to be updated, see TSVN rev. 21375
1902 SetBackgroundImage(IDI_RENAME_BKG);
1903 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_RENAME)));
1904 if (!Move(m_targetPathList, m_url, m_Revision, m_sMessage))
1906 ReportSVNError();
1907 return false;
1909 #endif
1910 return true;
1913 bool CGitProgressList::CmdResolve(CString& sWindowTitle, bool& localoperation)
1916 localoperation = true;
1917 ASSERT(m_targetPathList.GetCount() == 1);
1918 SetWindowTitle(IDS_PROGRS_TITLE_RESOLVE, g_Git.m_CurrentDir + _T("\\") + m_targetPathList.GetCommonRoot().GetUIPathString(), sWindowTitle);
1919 SetBackgroundImage(IDI_RESOLVE_BKG);
1920 // check if the file may still have conflict markers in it.
1921 //BOOL bMarkers = FALSE;
1923 m_itemCountTotal = m_targetPathList.GetCount();
1924 for (m_itemCount = 0; m_itemCount < m_itemCountTotal; ++m_itemCount)
1926 CString cmd,out,tempmergefile;
1927 cmd.Format(_T("git.exe add -f -- \"%s\""),m_targetPathList[m_itemCount].GetGitPathString());
1928 if (g_Git.Run(cmd, &out, CP_UTF8))
1930 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK|MB_ICONERROR);
1931 m_bErrorsOccurred=true;
1932 return false;
1935 CAppUtils::RemoveTempMergeFile((CTGitPath &)m_targetPathList[m_itemCount]);
1937 Notify(m_targetPathList[m_itemCount], git_wc_notify_resolved);
1939 #if 0
1940 if ((m_options & ProgOptSkipConflictCheck) == 0)
1944 for (INT_PTR fileindex=0; (fileindex<m_targetPathList.GetCount()) && (bMarkers==FALSE); ++fileindex)
1946 if (!m_targetPathList[fileindex].IsDirectory())
1948 CStdioFile file(m_targetPathList[fileindex].GetWinPath(), CFile::typeBinary | CFile::modeRead);
1949 CString strLine = _T("");
1950 while (file.ReadString(strLine))
1952 if (strLine.Find(_T("<<<<<<<"))==0)
1954 bMarkers = TRUE;
1955 break;
1958 file.Close();
1962 catch (CFileException* pE)
1964 TRACE(_T("CFileException in Resolve!\n"));
1965 TCHAR error[10000] = {0};
1966 pE->GetErrorMessage(error, 10000);
1967 ReportError(error);
1968 pE->Delete();
1969 return false;
1972 if (bMarkers)
1974 if (CMessageBox::Show(m_hWnd, IDS_PROGRS_REVERTMARKERS, IDS_APPNAME, MB_YESNO | MB_ICONQUESTION)==IDYES)
1976 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_RESOLVE)));
1977 for (INT_PTR fileindex=0; fileindex<m_targetPathList.GetCount(); ++fileindex)
1978 Resolve(m_targetPathList[fileindex], svn_wc_conflict_choose_merged, true);
1981 else
1983 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_RESOLVE)));
1984 for (INT_PTR fileindex=0; fileindex<m_targetPathList.GetCount(); ++fileindex)
1985 Resolve(m_targetPathList[fileindex], svn_wc_conflict_choose_merged, true);
1987 #endif
1988 CShellUpdater::Instance().AddPathsForUpdate(m_targetPathList);
1990 return true;
1993 bool CGitProgressList::CmdRevert(CString& sWindowTitle, bool& localoperation)
1996 localoperation = true;
1997 SetWindowTitle(IDS_PROGRS_TITLE_REVERT, g_Git.m_CurrentDir + _T("\\") + m_targetPathList.GetCommonRoot().GetUIPathString(), sWindowTitle);
1998 SetBackgroundImage(IDI_REVERT_BKG);
2000 m_itemCountTotal = 2 * m_selectedPaths.GetCount();
2001 CTGitPathList delList;
2002 for (m_itemCount = 0; m_itemCount < m_selectedPaths.GetCount(); ++m_itemCount)
2004 CTGitPath path;
2005 int action;
2006 path.SetFromWin(g_Git.m_CurrentDir + _T("\\") + m_selectedPaths[m_itemCount].GetWinPath());
2007 action = m_selectedPaths[m_itemCount].m_Action;
2008 /* rename file can't delete because it needs original file*/
2009 if((!(action & CTGitPath::LOGACTIONS_ADDED)) &&
2010 (!(action & CTGitPath::LOGACTIONS_REPLACED)))
2011 delList.AddPath(path);
2013 if (DWORD(CRegDWORD(_T("Software\\TortoiseGit\\RevertWithRecycleBin"), TRUE)))
2014 delList.DeleteAllFiles(true);
2016 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_REVERT)));
2017 for (int i = 0; i < m_selectedPaths.GetCount(); ++i)
2019 if(g_Git.Revert(_T("HEAD"), (CTGitPath&)m_selectedPaths[i]))
2021 CMessageBox::Show(NULL,_T("Revert Fail"),_T("TortoiseGit"),MB_OK|MB_ICONERROR);
2022 m_bErrorsOccurred=true;
2023 return false;
2025 Notify(m_selectedPaths[i], git_wc_notify_revert);
2026 ++m_itemCount;
2029 CShellUpdater::Instance().AddPathsForUpdate(m_selectedPaths);
2031 return true;
2034 bool CGitProgressList::CmdSwitch(CString& /*sWindowTitle*/, bool& /*localoperation*/)
2036 #if 0
2037 ASSERT(m_targetPathList.GetCount() == 1);
2038 SVNStatus st;
2039 sWindowTitle.LoadString(IDS_PROGRS_TITLE_SWITCH);
2040 SetWindowText(sWindowTitle); // needs to be updated, see TSVN rev. 21375
2041 SetBackgroundImage(IDI_SWITCH_BKG);
2042 LONG rev = 0;
2043 if (st.GetStatus(m_targetPathList[0]) != (-2))
2045 if (st.status->entry != NULL)
2047 rev = st.status->entry->revision;
2051 CString sCmdInfo;
2052 sCmdInfo.Format(IDS_PROGRS_CMD_SWITCH,
2053 m_targetPathList[0].GetWinPath(), (LPCTSTR)m_url.GetSVNPathString(),
2054 (LPCTSTR)m_Revision.ToString());
2055 ReportCmd(sCmdInfo);
2057 bool depthIsSticky = true;
2058 if (m_depth == svn_depth_unknown)
2059 depthIsSticky = false;
2060 if (!Switch(m_targetPathList[0], m_url, m_Revision, m_Revision, m_depth, depthIsSticky, m_options & ProgOptIgnoreExternals))
2062 ReportSVNError();
2063 return false;
2065 m_UpdateStartRevMap[m_targetPathList[0].GetSVNApiPath(pool)] = rev;
2066 if ((m_RevisionEnd >= 0)&&(rev >= 0)
2067 &&((LONG)m_RevisionEnd > (LONG)rev))
2069 GetDlgItem(IDC_LOGBUTTON)->ShowWindow(SW_SHOW);
2071 #endif
2072 return true;
2074 bool CGitProgressList::CmdClone(CString& sWindowTitle, bool& /*localoperation*/)
2076 if (!g_Git.UsingLibGit2(CGit::GIT_CMD_CLONE))
2078 // should never run to here
2079 ASSERT(FALSE);
2080 return false;
2082 this->m_TotalBytesTransferred = 0;
2084 SetWindowTitle(IDS_PROGRS_TITLE_CLONE, m_url.GetGitPathString(), sWindowTitle);
2085 SetBackgroundImage(IDI_SWITCH_BKG);
2086 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROG_CLONE)));
2088 if (m_url.IsEmpty() || m_targetPathList.GetCount() == 0)
2089 return false;
2091 CStringA url = CUnicodeUtils::GetMulti(m_url.GetGitPathString(), CP_UTF8);
2092 CStringA path = CUnicodeUtils::GetMulti(m_targetPathList[0].GetWinPathString(),CP_UTF8);
2094 git_repository *cloned_repo = NULL;
2095 git_remote *origin = NULL;
2096 git_checkout_opts checkout_opts = GIT_CHECKOUT_OPTS_INIT;
2098 int error = 0;
2100 git_clone_options clone_opts = GIT_CLONE_OPTIONS_INIT;
2102 clone_opts.checkout_opts = checkout_opts;
2104 clone_opts.checkout_opts.checkout_strategy = m_bNoCheckout? GIT_CHECKOUT_NONE : GIT_CHECKOUT_SAFE_CREATE;
2105 clone_opts.checkout_opts.progress_cb = CheckoutCallback;
2106 clone_opts.checkout_opts.progress_payload = this;
2108 git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
2110 callbacks.update_tips = RemoteUpdatetipsCallback;
2111 callbacks.progress = RemoteProgressCallback;
2112 callbacks.completion = RemoteCompletionCallback;
2113 callbacks.payload = this;
2115 clone_opts.remote_callbacks = &callbacks;
2117 if (!m_RefSpec.IsEmpty())
2118 clone_opts.checkout_branch = CUnicodeUtils::GetMulti(m_RefSpec, CP_UTF8).GetBuffer();
2120 clone_opts.fetch_progress_cb = FetchCallback;
2121 clone_opts.fetch_progress_payload = this;
2123 clone_opts.cred_acquire_cb = CAppUtils::Git2GetUserPassword;
2125 clone_opts.bare = m_bBare;
2127 if(m_pAnimate)
2129 m_pAnimate->ShowWindow(SW_SHOW);
2130 m_pAnimate->Play(0, INT_MAX, INT_MAX);
2132 error = git_clone(&cloned_repo, url, path, &clone_opts);
2134 if (m_pAnimate)
2136 m_pAnimate->Stop();
2137 m_pAnimate->ShowWindow(SW_HIDE);
2140 git_remote_free(origin);
2141 if (error)
2143 ReportGitError();
2144 return false;
2146 else if (cloned_repo)
2147 git_repository_free(cloned_repo);
2148 return true;
2150 bool CGitProgressList::CmdSendMail(CString& sWindowTitle, bool& /*localoperation*/)
2152 SetWindowTitle(IDS_PROGRS_TITLE_SENDMAIL, g_Git.m_CurrentDir + _T("\\") + m_targetPathList.GetCommonRoot().GetUIPathString(), sWindowTitle);
2153 //SetBackgroundImage(IDI_ADD_BKG);
2154 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_SENDMAIL)));
2155 bool ret=true;
2156 if(this->m_SendMailFlags&SENDMAIL_COMBINED)
2158 CString error;
2159 CTGitPath path;
2160 Notify(path,git_wc_notify_sendmail_start);
2161 CString err;
2162 int retry=0;
2163 while(retry <3)
2165 if(!!CPatch::SendPatchesCombined(m_targetPathList,m_SendMailTO,m_SendMailCC,m_SendMailSubject,!!(this->m_SendMailFlags&SENDMAIL_ATTACHMENT),!!(this->m_SendMailFlags&SENDMAIL_MAPI),&err))
2167 Notify(path,git_wc_notify_sendmail_error,ret,&err);
2168 ret = false;
2170 else
2172 break;
2175 ++retry;
2176 if (retry < 3)
2177 Notify(path,git_wc_notify_sendmail_retry,ret,&err);
2178 Sleep(2000);
2179 if(m_bCancelled)
2181 ReportUserCanceled();
2182 return false;
2185 if (ret)
2186 Notify(path,git_wc_notify_sendmail_done,ret);
2188 else
2190 m_itemCountTotal = m_targetPathList.GetCount();
2191 for (m_itemCount = 0; ret && m_itemCount < m_itemCountTotal; ++m_itemCount)
2193 CPatch patch;
2194 Notify(m_targetPathList[m_itemCount], git_wc_notify_sendmail_start);
2196 int retry=0;
2197 while(retry<3)
2199 if(!!patch.Send((CString&)m_targetPathList[m_itemCount].GetWinPathString(),this->m_SendMailTO,
2200 this->m_SendMailCC,!!(this->m_SendMailFlags&SENDMAIL_ATTACHMENT),!!(this->m_SendMailFlags&SENDMAIL_MAPI)))
2202 Notify(m_targetPathList[m_itemCount], git_wc_notify_sendmail_error, ret, &patch.m_LastError);
2203 ret = false;
2206 else
2208 ret = true;
2209 break;
2211 ++retry;
2212 if (retry < 3)
2213 Notify(m_targetPathList[m_itemCount], git_wc_notify_sendmail_retry, ret, &patch.m_LastError);
2214 Sleep(2000);
2215 if(m_bCancelled)
2217 ReportUserCanceled();
2218 return false;
2221 if (ret)
2222 Notify(m_targetPathList[m_itemCount], git_wc_notify_sendmail_done, ret);
2225 return ret;
2228 bool CGitProgressList::CmdFetch(CString& sWindowTitle, bool& /*localoperation*/)
2230 if (!g_Git.UsingLibGit2(CGit::GIT_CMD_CLONE))
2232 // should never run to here
2233 ASSERT(0);
2234 return false;
2236 this->m_TotalBytesTransferred = 0;
2238 SetWindowTitle(IDS_PROGRS_TITLE_FETCH, g_Git.m_CurrentDir, sWindowTitle);
2239 SetBackgroundImage(IDI_UPDATE_BKG);
2240 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_TITLE_FETCH)) + _T(" ") + m_url.GetGitPathString() + _T(" ") + m_RefSpec);
2242 CStringA url = CUnicodeUtils::GetMulti(m_url.GetGitPathString(), CP_UTF8);
2243 CStringA gitdir = CUnicodeUtils::GetMulti(CTGitPath(g_Git.m_CurrentDir).GetGitPathString(), CP_UTF8);
2244 CStringA remotebranch = CUnicodeUtils::GetMulti(m_RefSpec, CP_UTF8);
2246 git_remote *remote = NULL;
2247 git_repository *repo = NULL;
2248 bool ret = true;
2252 if (m_pAnimate)
2254 m_pAnimate->ShowWindow(SW_SHOW);
2255 m_pAnimate->Play(0, INT_MAX, INT_MAX);
2258 if (git_repository_open(&repo, gitdir.GetBuffer()))
2260 gitdir.ReleaseBuffer();
2261 ReportGitError();
2262 ret = false;
2263 break;
2266 // first try with a named remote (e.g. "origin")
2267 if (git_remote_load(&remote, repo, url) < 0)
2269 // retry with repository located at a specific url
2270 if (git_remote_create_inmemory(&remote, repo, NULL, url) < 0)
2272 ReportGitError();
2273 ret = false;
2274 break;
2278 git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
2280 callbacks.update_tips = RemoteUpdatetipsCallback;
2281 callbacks.progress = RemoteProgressCallback;
2282 callbacks.completion = RemoteCompletionCallback;
2283 callbacks.payload = this;
2285 git_remote_set_callbacks(remote, &callbacks);
2286 git_remote_set_cred_acquire_cb(remote, CAppUtils::Git2GetUserPassword, NULL);
2287 git_remote_set_autotag(remote, (git_remote_autotag_option_t)m_AutoTag);
2289 if (!remotebranch.IsEmpty() && git_remote_set_fetchspec(remote, remotebranch))
2291 ReportGitError();
2292 ret = false;
2293 break;
2296 // Connect to the remote end specifying that we want to fetch
2297 // information from it.
2298 if (git_remote_connect(remote, GIT_DIRECTION_FETCH) < 0) {
2299 ReportGitError();
2300 ret = false;
2301 break;
2304 // Download the packfile and index it. This function updates the
2305 // amount of received data and the indexer stats which lets you
2306 // inform the user about progress.
2307 if (git_remote_download(remote, FetchCallback, this) < 0) {
2308 ReportGitError();
2309 ret = false;
2310 break;
2313 if (m_pAnimate)
2315 m_pAnimate->ShowWindow(SW_HIDE);
2317 // Update the references in the remote's namespace to point to the
2318 // right commits. This may be needed even if there was no packfile
2319 // to download, which can happen e.g. when the branches have been
2320 // changed but all the neede objects are available locally.
2321 if (git_remote_update_tips(remote) < 0)
2323 ReportGitError();
2324 ret = false;
2325 break;
2328 git_remote_disconnect(remote);
2330 } while(0);
2332 git_remote_free(remote);
2333 git_repository_free(repo);
2334 if (m_pAnimate)
2335 m_pAnimate->ShowWindow(SW_HIDE);
2336 return ret;
2340 void CGitProgressList::Init()
2342 SetExtendedStyle (LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER);
2344 DeleteAllItems();
2345 int c = ((CHeaderCtrl*)(GetDlgItem(0)))->GetItemCount()-1;
2346 while (c>=0)
2347 DeleteColumn(c--);
2349 CString temp;
2350 temp.LoadString(IDS_PROGRS_ACTION);
2351 InsertColumn(0, temp);
2352 temp.LoadString(IDS_PROGRS_PATH);
2353 InsertColumn(1, temp);
2355 m_pThread = AfxBeginThread(ProgressThreadEntry, this, THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED);
2356 if (m_pThread==NULL)
2358 ReportError(CString(MAKEINTRESOURCE(IDS_ERR_THREADSTARTFAILED)));
2360 else
2362 m_pThread->m_bAutoDelete = FALSE;
2363 m_pThread->ResumeThread();
2366 // Call this early so that the column headings aren't hidden before any
2367 // text gets added.
2368 ResizeColumns();
2370 SetTimer(VISIBLETIMER, 300, NULL);
2374 void CGitProgressList::OnClose()
2376 // TODO: Add your message handler code here and/or call default
2377 if (m_bCancelled)
2379 TerminateThread(m_pThread->m_hThread, (DWORD)-1);
2380 InterlockedExchange(&m_bThreadRunning, FALSE);
2382 else
2384 m_bCancelled = TRUE;
2385 return;
2387 CListCtrl::OnClose();
2391 BOOL CGitProgressList::PreTranslateMessage(MSG* pMsg)
2393 // TODO: Add your specialized code here and/or call the base class
2394 if (pMsg->message == WM_KEYDOWN)
2396 if (pMsg->wParam == 'A')
2398 if (GetKeyState(VK_CONTROL)&0x8000)
2400 // Ctrl-A -> select all
2401 SetSelectionMark(0);
2402 for (int i=0; i<GetItemCount(); ++i)
2404 SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
2408 if ((pMsg->wParam == 'C')||(pMsg->wParam == VK_INSERT))
2410 int selIndex = GetSelectionMark();
2411 if (selIndex >= 0)
2413 if (GetKeyState(VK_CONTROL)&0x8000)
2415 //Ctrl-C -> copy to clipboard
2416 CString sClipdata;
2417 POSITION pos = GetFirstSelectedItemPosition();
2418 if (pos != NULL)
2420 while (pos)
2422 int nItem = GetNextSelectedItem(pos);
2423 CString sAction = GetItemText(nItem, 0);
2424 CString sPath = GetItemText(nItem, 1);
2425 CString sMime = GetItemText(nItem, 2);
2426 CString sLogCopyText;
2427 sLogCopyText.Format(_T("%s: %s %s\r\n"),
2428 (LPCTSTR)sAction, (LPCTSTR)sPath, (LPCTSTR)sMime);
2429 sClipdata += sLogCopyText;
2431 CStringUtils::WriteAsciiStringToClipboard(sClipdata);
2436 } // if (pMsg->message == WM_KEYDOWN)
2437 return CListCtrl::PreTranslateMessage(pMsg);
2440 CString CGitProgressList::GetPathFromColumnText(const CString& sColumnText)
2442 CString sPath = sColumnText;
2443 if (sPath.Find(':')<0)
2445 // the path is not absolute: add the common root of all paths to it
2446 sPath = g_Git.m_CurrentDir + _T("\\") + sColumnText;
2448 return sPath;
2451 void CGitProgressList::SetWindowTitle(UINT id, const CString& urlorpath, CString& dialogname)
2453 if (!m_bSetTitle || !m_pPostWnd)
2454 return;
2456 dialogname.LoadString(id);
2457 CAppUtils::SetWindowTitle(m_pPostWnd->GetSafeHwnd(), urlorpath, dialogname);