Revision Graph: Add filter to walk from Only Local Branches
[TortoiseGit.git] / src / TortoiseProc / GitProgressList.cpp
blob6b69ca440739d45ded795690a73d53e1beeec29d
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2014 - 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 "registry.h"
27 #include "AppUtils.h"
28 #include "StringUtils.h"
29 #include "SoundUtils.h"
30 #include "LogFile.h"
31 #include "ShellUpdater.h"
32 #include "IconMenu.h"
33 #include "Patch.h"
34 #include "MassiveGitTask.h"
35 #include "LoglistUtils.h"
37 BOOL CGitProgressList::m_bAscending = FALSE;
38 int CGitProgressList::m_nSortedColumn = -1;
40 #define TRANSFERTIMER 100
41 #define VISIBLETIMER 101
42 // CGitProgressList
44 enum GITProgressDlgContextMenuCommands
46 // needs to start with 1, since 0 is the return value if *nothing* is clicked on in the context menu
47 ID_COMPARE = 1,
48 ID_EDITCONFLICT,
49 ID_CONFLICTRESOLVE,
50 ID_CONFLICTUSETHEIRS,
51 ID_CONFLICTUSEMINE,
52 ID_LOG,
53 ID_OPEN,
54 ID_OPENWITH,
55 ID_EXPLORE,
56 ID_COPY
59 IMPLEMENT_DYNAMIC(CGitProgressList, CListCtrl)
61 class CSmartAnimation
63 CAnimateCtrl* m_pAnimate;
65 public:
66 CSmartAnimation(CAnimateCtrl* pAnimate)
68 m_pAnimate = pAnimate;
69 if (m_pAnimate)
71 m_pAnimate->ShowWindow(SW_SHOW);
72 m_pAnimate->Play(0, INT_MAX, INT_MAX);
75 ~CSmartAnimation()
77 if (m_pAnimate)
79 m_pAnimate->Stop();
80 m_pAnimate->ShowWindow(SW_HIDE);
85 CGitProgressList::CGitProgressList():CListCtrl()
86 , m_bCancelled(FALSE)
87 , m_pThread(NULL)
88 , m_bErrorsOccurred(false)
89 , m_bBare(false)
90 , m_bNoCheckout(false)
91 , m_AutoTag(GIT_REMOTE_DOWNLOAD_TAGS_AUTO)
92 , m_resetType(0)
93 , m_options(ProgOptNone)
94 , m_bSetTitle(false)
95 , m_pTaskbarList(nullptr)
96 , m_SendMail(nullptr)
97 , m_Command(GitProgress_none)
98 , m_bThreadRunning(FALSE)
99 , m_keepchangelist(false)
100 , m_nConflicts(0)
101 , m_bMergesAddsDeletesOccurred(FALSE)
102 , iFirstResized(0)
103 , bSecondResized(false)
104 , nEnsureVisibleCount(0)
105 , m_TotalBytesTransferred(0)
106 , m_bFinishedItemAdded(false)
107 , m_bLastVisible(false)
108 , m_itemCount(-1)
109 , m_itemCountTotal(-1)
111 m_pInfoCtrl = nullptr;
112 m_pAnimate = nullptr;
113 m_pProgControl = nullptr;
114 m_pProgressLabelCtrl = nullptr;
115 m_pPostWnd = nullptr;
116 m_columnbuf[0] = 0;
119 CGitProgressList::~CGitProgressList()
121 for (size_t i = 0; i < m_arData.size(); ++i)
123 delete m_arData[i];
125 if(m_pThread != NULL)
127 delete m_pThread;
132 BEGIN_MESSAGE_MAP(CGitProgressList, CListCtrl)
133 ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnNMCustomdrawSvnprogress)
134 ON_NOTIFY_REFLECT(NM_DBLCLK, OnNMDblclkSvnprogress)
135 ON_NOTIFY_REFLECT(HDN_ITEMCLICK, OnHdnItemclickSvnprogress)
136 ON_NOTIFY_REFLECT(LVN_BEGINDRAG, OnLvnBegindragSvnprogress)
137 ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnLvnGetdispinfoSvnprogress)
138 ON_MESSAGE(WM_SHOWCONFLICTRESOLVER, OnShowConflictResolver)
139 ON_WM_SIZE()
140 ON_WM_TIMER()
141 ON_WM_CONTEXTMENU()
142 ON_WM_CLOSE()
143 END_MESSAGE_MAP()
145 void CGitProgressList::Cancel()
147 m_bCancelled = TRUE;
152 // CGitProgressList message handlers
155 LRESULT CGitProgressList::OnShowConflictResolver(WPARAM /*wParam*/, LPARAM /*lParam*/)
157 #if 0
158 CConflictResolveDlg dlg(this);
159 const svn_wc_conflict_description_t *description = (svn_wc_conflict_description_t *)lParam;
160 if (description)
162 dlg.SetConflictDescription(description);
163 if (m_pTaskbarList)
165 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_PAUSED);
167 if (dlg.DoModal() == IDOK)
169 if (dlg.GetResult() == svn_wc_conflict_choose_postpone)
171 // if the result is conflicted and the dialog returned IDOK,
172 // that means we should not ask again in case of a conflict
173 m_AlwaysConflicted = true;
174 ::SendMessage(GetDlgItem(IDC_NONINTERACTIVE)->GetSafeHwnd(), BM_SETCHECK, BST_CHECKED, 0);
177 m_mergedfile = dlg.GetMergedFile();
178 m_bCancelled = dlg.IsCancelled();
179 if (m_pTaskbarList)
180 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_INDETERMINATE);
181 return dlg.GetResult();
184 return svn_wc_conflict_choose_postpone;
185 #endif
186 return 0;
188 #if 0
189 svn_wc_conflict_choice_t CGitProgressList::ConflictResolveCallback(const svn_wc_conflict_description_t *description, CString& mergedfile)
191 // we only bother the user when merging
192 if (((m_Command == GitProgress_Merge)||(m_Command == GitProgress_MergeAll)||(m_Command == GitProgress_MergeReintegrate))&&(!m_AlwaysConflicted)&&(description))
194 // we're in a worker thread here. That means we must not show a dialog from the thread
195 // but let the UI thread do it.
196 // To do that, we send a message to the UI thread and let it show the conflict resolver dialog.
197 LRESULT dlgResult = ::SendMessage(GetSafeHwnd(), WM_SHOWCONFLICTRESOLVER, 0, (LPARAM)description);
198 mergedfile = m_mergedfile;
199 return (svn_wc_conflict_choice_t)dlgResult;
202 return svn_wc_conflict_choose_postpone;
204 #endif
205 void CGitProgressList::AddItemToList()
207 int totalcount = GetItemCount();
209 SetItemCountEx(totalcount+1, LVSICF_NOSCROLL|LVSICF_NOINVALIDATEALL);
210 // make columns width fit
211 if (iFirstResized < 30)
213 // only resize the columns for the first 30 or so entries.
214 // after that, don't resize them anymore because that's an
215 // expensive function call and the columns will be sized
216 // close enough already.
217 ResizeColumns();
218 ++iFirstResized;
221 // Make sure the item is *entirely* visible even if the horizontal
222 // scroll bar is visible.
223 int count = GetCountPerPage();
224 if (totalcount <= (GetTopIndex() + count + nEnsureVisibleCount + 2))
226 ++nEnsureVisibleCount;
227 m_bLastVisible = true;
229 else
231 nEnsureVisibleCount = 0;
232 if (IsIconic() == 0)
233 m_bLastVisible = false;
238 BOOL CGitProgressList::Notify(const CTGitPath& path, git_wc_notify_action_t action)
240 bool bNoNotify = false;
241 bool bDoAddData = true;
242 NotificationData * data = new NotificationData();
243 data->path = path;
244 data->action = action;
245 data->sPathColumnText=path.GetGitPathString();
246 data->bAuxItem = false;
248 if (this->m_pAnimate)
249 this->m_pAnimate->ShowWindow(SW_HIDE);
251 switch (data->action)
253 case git_wc_notify_add:
254 data->sActionColumnText.LoadString(IDS_SVNACTION_ADD);
255 data->color = m_Colors.GetColor(CColors::Added);
256 break;
257 case git_wc_notify_sendmail:
258 data->sActionColumnText.LoadString(IDS_SVNACTION_SENDMAIL_START);
259 data->color = m_Colors.GetColor(CColors::Modified);
260 break;
262 case git_wc_notify_resolved:
263 data->sActionColumnText.LoadString(IDS_SVNACTION_RESOLVE);
264 break;
266 case git_wc_notify_revert:
267 data->sActionColumnText.LoadString(IDS_SVNACTION_REVERT);
268 break;
270 case git_wc_notify_checkout:
271 data->sActionColumnText.LoadString(IDS_PROGRS_CMD_CHECKOUT);
272 data->color = m_Colors.GetColor(CColors::Added);
273 data->bAuxItem = false;
274 break;
275 default:
276 break;
277 } // switch (data->action)
279 if (bNoNotify)
280 delete data;
281 else
283 if (bDoAddData)
285 m_arData.push_back(data);
286 AddItemToList();
287 if ((!data->bAuxItem) && (m_itemCount > 0))
289 if (m_pProgControl)
291 m_pProgControl->ShowWindow(SW_SHOW);
292 m_pProgControl->SetPos(m_itemCount);
293 m_pProgControl->SetRange32(0, m_itemCountTotal);
295 if (m_pTaskbarList && m_pPostWnd)
297 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_NORMAL);
298 m_pTaskbarList->SetProgressValue(m_pPostWnd->GetSafeHwnd(), m_itemCount, m_itemCountTotal);
302 //if ((action == svn_wc_notify_commit_postfix_txdelta)&&(bSecondResized == FALSE))
304 // ResizeColumns();
305 // bSecondResized = TRUE;
309 return TRUE;
313 CString CGitProgressList::BuildInfoString()
315 CString infotext;
316 if(this->m_Command == GitProgress_Resolve)
317 infotext = _T("You need commit your change after resolve conflict");
318 #if 0
320 CString temp;
321 int added = 0;
322 int copied = 0;
323 int deleted = 0;
324 int restored = 0;
325 int reverted = 0;
326 int resolved = 0;
327 int conflicted = 0;
328 int updated = 0;
329 int merged = 0;
330 int modified = 0;
331 int skipped = 0;
332 int replaced = 0;
334 for (size_t i=0; i<m_arData.size(); ++i)
336 const NotificationData * dat = m_arData[i];
337 switch (dat->action)
339 case svn_wc_notify_add:
340 case svn_wc_notify_update_add:
341 case svn_wc_notify_commit_added:
342 if (dat->bConflictedActionItem)
343 ++conflicted;
344 else
345 ++added;
346 break;
347 case svn_wc_notify_copy:
348 ++copied;
349 break;
350 case svn_wc_notify_delete:
351 case svn_wc_notify_update_delete:
352 case svn_wc_notify_commit_deleted:
353 ++deleted;
354 break;
355 case svn_wc_notify_restore:
356 ++restored;
357 break;
358 case svn_wc_notify_revert:
359 ++reverted;
360 break;
361 case svn_wc_notify_resolved:
362 ++resolved;
363 break;
364 case svn_wc_notify_update_update:
365 if (dat->bConflictedActionItem)
366 ++conflicted;
367 else if ((dat->content_state == svn_wc_notify_state_merged) || (dat->prop_state == svn_wc_notify_state_merged))
368 ++merged;
369 else
370 ++updated;
371 break;
372 case svn_wc_notify_commit_modified:
373 ++modified;
374 break;
375 case svn_wc_notify_skip:
376 ++skipped;
377 break;
378 case svn_wc_notify_commit_replaced:
379 ++replaced;
380 break;
383 if (conflicted)
385 temp.LoadString(IDS_SVNACTION_CONFLICTED);
386 infotext += temp;
387 temp.Format(_T(":%d "), conflicted);
388 infotext += temp;
390 if (skipped)
392 temp.LoadString(IDS_SVNACTION_SKIP);
393 infotext += temp;
394 infotext.AppendFormat(_T(":%d "), skipped);
396 if (merged)
398 temp.LoadString(IDS_SVNACTION_MERGED);
399 infotext += temp;
400 infotext.AppendFormat(_T(":%d "), merged);
402 if (added)
404 temp.LoadString(IDS_SVNACTION_ADD);
405 infotext += temp;
406 infotext.AppendFormat(_T(":%d "), added);
408 if (deleted)
410 temp.LoadString(IDS_SVNACTION_DELETE);
411 infotext += temp;
412 infotext.AppendFormat(_T(":%d "), deleted);
414 if (modified)
416 temp.LoadString(IDS_SVNACTION_MODIFIED);
417 infotext += temp;
418 infotext.AppendFormat(_T(":%d "), modified);
420 if (copied)
422 temp.LoadString(IDS_SVNACTION_COPY);
423 infotext += temp;
424 infotext.AppendFormat(_T(":%d "), copied);
426 if (replaced)
428 temp.LoadString(IDS_SVNACTION_REPLACED);
429 infotext += temp;
430 infotext.AppendFormat(_T(":%d "), replaced);
432 if (updated)
434 temp.LoadString(IDS_SVNACTION_UPDATE);
435 infotext += temp;
436 infotext.AppendFormat(_T(":%d "), updated);
438 if (restored)
440 temp.LoadString(IDS_SVNACTION_RESTORE);
441 infotext += temp;
442 infotext.AppendFormat(_T(":%d "), restored);
444 if (reverted)
446 temp.LoadString(IDS_SVNACTION_REVERT);
447 infotext += temp;
448 infotext.AppendFormat(_T(":%d "), reverted);
450 if (resolved)
452 temp.LoadString(IDS_SVNACTION_RESOLVE);
453 infotext += temp;
454 infotext.AppendFormat(_T(":%d "), resolved);
456 #endif
457 return infotext;
460 void CGitProgressList::SetSelectedList(const CTGitPathList& selPaths)
462 m_selectedPaths = selPaths;
465 void CGitProgressList::ResizeColumns()
467 SetRedraw(FALSE);
469 TCHAR textbuf[MAX_PATH] = {0};
471 CHeaderCtrl * pHeaderCtrl = (CHeaderCtrl*)(GetDlgItem(0));
472 if (pHeaderCtrl)
474 int maxcol = pHeaderCtrl->GetItemCount()-1;
475 for (int col = 0; col <= maxcol; ++col)
477 // find the longest width of all items
478 int count = GetItemCount();
479 HDITEM hdi = {0};
480 hdi.mask = HDI_TEXT;
481 hdi.pszText = textbuf;
482 hdi.cchTextMax = _countof(textbuf);
483 pHeaderCtrl->GetItem(col, &hdi);
484 int cx = GetStringWidth(hdi.pszText)+20; // 20 pixels for col separator and margin
486 for (int index = 0; index<count; ++index)
488 // get the width of the string and add 12 pixels for the column separator and margins
489 int linewidth = cx;
490 switch (col)
492 case 0:
493 linewidth = GetStringWidth(m_arData[index]->sActionColumnText) + 12;
494 break;
495 case 1:
496 linewidth = GetStringWidth(m_arData[index]->sPathColumnText) + 12;
497 break;
499 if (cx < linewidth)
500 cx = linewidth;
502 SetColumnWidth(col, cx);
506 SetRedraw(TRUE);
509 bool CGitProgressList::SetBackgroundImage(UINT nID)
511 return CAppUtils::SetListCtrlBackgroundImage(GetSafeHwnd(), nID);
514 void CGitProgressList::ReportGitError()
516 ReportError(CGit::GetLibGit2LastErr());
519 void CGitProgressList::ReportUserCanceled()
521 ReportError(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED)));
524 void CGitProgressList::ReportError(const CString& sError)
526 CSoundUtils::PlayTGitError();
527 ReportString(sError, CString(MAKEINTRESOURCE(IDS_ERR_ERROR)), m_Colors.GetColor(CColors::Conflict));
528 m_bErrorsOccurred = true;
531 void CGitProgressList::ReportWarning(const CString& sWarning)
533 CSoundUtils::PlayTGitWarning();
534 ReportString(sWarning, CString(MAKEINTRESOURCE(IDS_WARN_WARNING)), m_Colors.GetColor(CColors::Conflict));
537 void CGitProgressList::ReportNotification(const CString& sNotification)
539 CSoundUtils::PlayTGitNotification();
540 ReportString(sNotification, CString(MAKEINTRESOURCE(IDS_WARN_NOTE)));
543 void CGitProgressList::ReportCmd(const CString& sCmd)
545 ReportString(sCmd, CString(MAKEINTRESOURCE(IDS_PROGRS_CMDINFO)), m_Colors.GetColor(CColors::Cmd));
548 void CGitProgressList::ReportString(CString sMessage, const CString& sMsgKind, COLORREF color)
550 // instead of showing a dialog box with the error message or notification,
551 // just insert the error text into the list control.
552 // that way the user isn't 'interrupted' by a dialog box popping up!
554 // the message may be split up into different lines
555 // so add a new entry for each line of the message
556 while (!sMessage.IsEmpty())
558 NotificationData * data = new NotificationData();
559 data->bAuxItem = true;
560 data->sActionColumnText = sMsgKind;
561 if (sMessage.Find('\n')>=0)
562 data->sPathColumnText = sMessage.Left(sMessage.Find('\n'));
563 else
564 data->sPathColumnText = sMessage;
565 data->sPathColumnText.Trim(_T("\n\r"));
566 data->color = color;
567 if (sMessage.Find('\n')>=0)
569 sMessage = sMessage.Mid(sMessage.Find('\n'));
570 sMessage.Trim(_T("\n\r"));
572 else
573 sMessage.Empty();
574 m_arData.push_back(data);
575 AddItemToList();
579 UINT CGitProgressList::ProgressThreadEntry(LPVOID pVoid)
581 return ((CGitProgressList*)pVoid)->ProgressThread();
584 UINT CGitProgressList::ProgressThread()
586 // The SetParams function should have loaded something for us
588 CString temp;
589 CString sWindowTitle;
590 bool localoperation = false;
591 bool bSuccess = false;
593 if(m_pPostWnd)
594 m_pPostWnd->PostMessage(WM_PROG_CMD_START, m_Command);
596 if(m_pProgressLabelCtrl)
598 m_pProgressLabelCtrl->ShowWindow(SW_SHOW);
599 m_pProgressLabelCtrl->SetWindowText(_T(""));
602 // SetAndClearProgressInfo(m_hWnd);
603 m_itemCount = m_itemCountTotal;
605 InterlockedExchange(&m_bThreadRunning, TRUE);
606 iFirstResized = 0;
607 bSecondResized = FALSE;
608 m_bFinishedItemAdded = false;
609 DWORD startTime = GetCurrentTime();
611 if (m_pTaskbarList && m_pPostWnd)
612 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_INDETERMINATE);
614 switch (m_Command)
616 case GitProgress_Add:
617 bSuccess = CmdAdd(sWindowTitle, localoperation);
618 break;
619 case GitProgress_Resolve:
620 bSuccess = CmdResolve(sWindowTitle, localoperation);
621 break;
622 case GitProgress_Revert:
623 bSuccess = CmdRevert(sWindowTitle, localoperation);
624 break;
625 case GitProgress_SendMail:
626 bSuccess = CmdSendMail(sWindowTitle, localoperation);
627 break;
628 case GitProgress_Clone:
629 bSuccess = CmdClone(sWindowTitle, localoperation);
630 break;
631 case GitProgress_Fetch:
632 bSuccess = CmdFetch(sWindowTitle, localoperation);
633 break;
634 case GitProgress_Reset:
635 bSuccess = CmdReset(sWindowTitle, localoperation);
636 break;
638 if (!bSuccess)
639 temp.LoadString(IDS_PROGRS_TITLEFAILED);
640 else
641 temp.LoadString(IDS_PROGRS_TITLEFIN);
642 sWindowTitle = sWindowTitle + _T(" ") + temp;
643 if (m_bSetTitle && m_pPostWnd)
644 ::SetWindowText(m_pPostWnd->GetSafeHwnd(), sWindowTitle);
646 KillTimer(TRANSFERTIMER);
647 KillTimer(VISIBLETIMER);
649 if (m_pTaskbarList && m_pPostWnd)
651 if (DidErrorsOccur())
653 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_ERROR);
654 m_pTaskbarList->SetProgressValue(m_pPostWnd->GetSafeHwnd(), 100, 100);
656 else
657 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_NOPROGRESS);
660 CString info = BuildInfoString();
661 if (!bSuccess)
662 info.LoadString(IDS_PROGRS_INFOFAILED);
663 if (m_pInfoCtrl)
664 m_pInfoCtrl->SetWindowText(info);
666 ResizeColumns();
668 DWORD time = GetCurrentTime() - startTime;
670 CString sFinalInfo;
671 if (!m_sTotalBytesTransferred.IsEmpty())
673 temp.Format(IDS_PROGRS_TIME, (time / 1000) / 60, (time / 1000) % 60);
674 sFinalInfo.Format(IDS_PROGRS_FINALINFO, m_sTotalBytesTransferred, (LPCTSTR)temp);
675 if (m_pProgressLabelCtrl)
676 m_pProgressLabelCtrl->SetWindowText(sFinalInfo);
678 else
680 if (m_pProgressLabelCtrl)
681 m_pProgressLabelCtrl->ShowWindow(SW_HIDE);
684 if (m_pProgControl)
685 m_pProgControl->ShowWindow(SW_HIDE);
687 if (!m_bFinishedItemAdded)
689 CString log, str;
690 if (bSuccess)
691 str.LoadString(IDS_SUCCESS);
692 else
693 str.LoadString(IDS_FAIL);
694 log.Format(_T("%s (%lu ms @ %s)"), str, time, CLoglistUtils::FormatDateAndTime(CTime::GetCurrentTime(), DATE_SHORTDATE, true, false));
696 // there's no "finished: xxx" line at the end. We add one here to make
697 // sure the user sees that the command is actually finished.
698 ReportString(log, CString(MAKEINTRESOURCE(IDS_PROGRS_FINISHED)), bSuccess? RGB(0,0,255) : RGB(255,0,0));
701 int count = GetItemCount();
702 if ((count > 0)&&(m_bLastVisible))
703 EnsureVisible(count-1, FALSE);
705 CLogFile logfile(g_Git.m_CurrentDir);
706 if (logfile.Open())
708 logfile.AddTimeLine();
709 for (size_t i = 0; i < m_arData.size(); ++i)
711 NotificationData * data = m_arData[i];
712 temp.Format(_T("%-20s : %s"), (LPCTSTR)data->sActionColumnText, (LPCTSTR)data->sPathColumnText);
713 logfile.AddLine(temp);
715 if (!sFinalInfo.IsEmpty())
716 logfile.AddLine(sFinalInfo);
717 logfile.Close();
720 m_bCancelled = TRUE;
721 InterlockedExchange(&m_bThreadRunning, FALSE);
722 #if 0 //need
723 RefreshCursor();
724 #endif
726 if (m_pPostWnd)
727 m_pPostWnd->PostMessage(WM_PROG_CMD_FINISH, this->m_Command, 0L);
729 //Don't do anything here which might cause messages to be sent to the window
730 //The window thread is probably now blocked in OnOK if we've done an auto close
731 return 0;
734 void CGitProgressList::OnLvnGetdispinfoSvnprogress(NMHDR *pNMHDR, LRESULT *pResult)
736 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
738 if (pDispInfo)
740 if (pDispInfo->item.mask & LVIF_TEXT)
742 if (pDispInfo->item.iItem < (int)m_arData.size())
744 const NotificationData * data = m_arData[pDispInfo->item.iItem];
745 switch (pDispInfo->item.iSubItem)
747 case 0:
748 lstrcpyn(m_columnbuf, data->sActionColumnText, MAX_PATH);
749 break;
750 case 1:
751 lstrcpyn(m_columnbuf, data->sPathColumnText, pDispInfo->item.cchTextMax);
752 if (!data->bAuxItem)
754 int cWidth = GetColumnWidth(1);
755 cWidth = max(12, cWidth-12);
756 CDC * pDC = GetDC();
757 if (pDC != NULL)
759 CFont * pFont = pDC->SelectObject(GetFont());
760 PathCompactPath(pDC->GetSafeHdc(), m_columnbuf, cWidth);
761 pDC->SelectObject(pFont);
762 ReleaseDC(pDC);
765 break;
766 default:
767 m_columnbuf[0] = 0;
769 pDispInfo->item.pszText = m_columnbuf;
773 *pResult = 0;
776 void CGitProgressList::OnNMCustomdrawSvnprogress(NMHDR *pNMHDR, LRESULT *pResult)
778 NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );
780 // Take the default processing unless we set this to something else below.
781 *pResult = CDRF_DODEFAULT;
783 // First thing - check the draw stage. If it's the control's prepaint
784 // stage, then tell Windows we want messages for every item.
786 if ( CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage )
788 *pResult = CDRF_NOTIFYITEMDRAW;
790 else if ( CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage )
792 // This is the prepaint stage for an item. Here's where we set the
793 // item's text color. Our return value will tell Windows to draw the
794 // item itself, but it will use the new color we set here.
796 // Tell Windows to paint the control itself.
797 *pResult = CDRF_DODEFAULT;
799 ASSERT(pLVCD->nmcd.dwItemSpec < m_arData.size());
800 if(pLVCD->nmcd.dwItemSpec >= m_arData.size())
802 return;
804 const NotificationData * data = m_arData[pLVCD->nmcd.dwItemSpec];
805 ASSERT(data != NULL);
806 if (data == NULL)
807 return;
809 // Store the color back in the NMLVCUSTOMDRAW struct.
810 pLVCD->clrText = data->color;
814 void CGitProgressList::OnNMDblclkSvnprogress(NMHDR * /*pNMHDR*/, LRESULT * /*pResult*/)
816 #if 0
817 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
818 *pResult = 0;
819 if (pNMLV->iItem < 0)
820 return;
821 if (m_options & ProgOptDryRun)
822 return; //don't do anything in a dry-run.
824 const NotificationData * data = m_arData[pNMLV->iItem];
825 if (data == NULL)
826 return;
828 if (data->bConflictedActionItem)
830 // We've double-clicked on a conflicted item - do a three-way merge on it
831 SVNDiff::StartConflictEditor(data->path);
833 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))
835 // This is a modified file which has been merged on update. Diff it against base
836 CTGitPath temporaryFile;
837 SVNDiff diff(this, this->m_hWnd, true);
838 diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
839 svn_revnum_t baseRev = 0;
840 diff.DiffFileAgainstBase(data->path, baseRev);
842 else if ((!data->bAuxItem)&&(data->path.Exists())&&(!data->path.IsDirectory()))
844 bool bOpenWith = false;
845 int ret = (int)ShellExecute(m_hWnd, NULL, data->path.GetWinPath(), NULL, NULL, SW_SHOWNORMAL);
846 if (ret <= HINSTANCE_ERROR)
847 bOpenWith = true;
848 if (bOpenWith)
850 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
851 cmd += data->path.GetWinPathString() + _T(" ");
852 CAppUtils::LaunchApplication(cmd, NULL, false);
855 #endif
858 void CGitProgressList::OnHdnItemclickSvnprogress(NMHDR *pNMHDR, LRESULT *pResult)
860 LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
861 if (m_bThreadRunning)
862 return;
863 if (m_nSortedColumn == phdr->iItem)
864 m_bAscending = !m_bAscending;
865 else
866 m_bAscending = TRUE;
867 m_nSortedColumn = phdr->iItem;
868 Sort();
870 CString temp;
871 SetRedraw(FALSE);
872 DeleteAllItems();
873 SetItemCountEx (static_cast<int>(m_arData.size()));
875 SetRedraw(TRUE);
877 *pResult = 0;
880 bool CGitProgressList::NotificationDataIsAux(const NotificationData* pData)
882 return pData->bAuxItem;
884 BOOL CGitProgressList::Notify(const git_wc_notify_action_t action, CString str, const git_oid *a, const git_oid *b)
886 NotificationData * data = new NotificationData();
887 data->action = action;
888 data->bAuxItem = false;
890 if (action == git_wc_notify_update_ref)
892 data->m_NewHash = b->id;
893 data->m_OldHash = a->id;
894 data->sActionColumnText.LoadString(IDS_GITACTION_UPDATE_REF);
895 data->sPathColumnText.Format(_T("%s\t %s -> %s"), str,
896 data->m_OldHash.ToString().Left(g_Git.GetShortHASHLength()),
897 data->m_NewHash.ToString().Left(g_Git.GetShortHASHLength()));
900 m_arData.push_back(data);
901 AddItemToList();
903 if (m_pAnimate)
904 m_pAnimate->Stop();
905 if (m_pAnimate)
906 m_pAnimate->ShowWindow(SW_HIDE);
907 return TRUE;
909 BOOL CGitProgressList::Notify(const git_wc_notify_action_t /*action*/, const git_transfer_progress *stat)
911 static unsigned int start = 0;
912 unsigned int dt = GetCurrentTime() - start;
913 double speed = 0;
915 if (m_bCancelled)
916 return FALSE;
918 if (dt > 100)
920 start = GetCurrentTime();
921 size_t ds = stat->received_bytes - m_TotalBytesTransferred;
922 speed = ds * 1000.0/dt;
923 m_TotalBytesTransferred = stat->received_bytes;
925 else
927 return TRUE;
930 int progress;
931 progress = stat->received_objects + stat->indexed_objects;
933 if ((stat->total_objects > 1000) && m_pProgControl && (!m_pProgControl->IsWindowVisible()))
935 if (m_pProgControl)
936 m_pProgControl->ShowWindow(SW_SHOW);
939 if (m_pProgressLabelCtrl && m_pProgressLabelCtrl->IsWindowVisible())
940 m_pProgressLabelCtrl->ShowWindow(SW_SHOW);
942 if (m_pProgControl)
944 m_pProgControl->SetPos(progress);
945 m_pProgControl->SetRange32(0, 2 * stat->total_objects);
947 if (m_pTaskbarList && m_pPostWnd)
949 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_NORMAL);
950 m_pTaskbarList->SetProgressValue(m_pPostWnd->GetSafeHwnd(), progress, 2 * stat->total_objects);
953 CString progText;
954 if (stat->received_bytes < 1024)
955 m_sTotalBytesTransferred.Format(IDS_SVN_PROGRESS_TOTALBYTESTRANSFERRED, (int64_t)stat->received_bytes);
956 else if (stat->received_bytes < 1200000)
957 m_sTotalBytesTransferred.Format(IDS_SVN_PROGRESS_TOTALTRANSFERRED, (int64_t)stat->received_bytes / 1024);
958 else
959 m_sTotalBytesTransferred.Format(IDS_SVN_PROGRESS_TOTALMBTRANSFERRED, (double)((double)stat->received_bytes / 1024000.0));
961 CString str;
962 if(speed < 1024)
963 str.Format(_T("%fB/s"), speed);
964 else if(speed < 1024 * 1024)
965 str.Format(_T("%.2fKB/s"), speed / 1024);
966 else
967 str.Format(_T("%.2fMB/s"), speed / 1024000.0);
969 progText.Format(IDS_SVN_PROGRESS_TOTALANDSPEED, (LPCTSTR)m_sTotalBytesTransferred, (LPCTSTR)str);
970 if (m_pProgressLabelCtrl)
971 m_pProgressLabelCtrl->SetWindowText(progText);
973 return TRUE;
976 void CGitProgressList::OnTimer(UINT_PTR nIDEvent)
978 if (nIDEvent == TRANSFERTIMER)
980 CString progText;
981 CString progSpeed;
982 progSpeed.Format(IDS_SVN_PROGRESS_BYTES_SEC, 0);
983 progText.Format(IDS_SVN_PROGRESS_TOTALANDSPEED, (LPCTSTR)m_sTotalBytesTransferred, (LPCTSTR)progSpeed);
984 if (m_pProgressLabelCtrl)
985 m_pProgressLabelCtrl->SetWindowText(progText);
987 KillTimer(TRANSFERTIMER);
989 if (nIDEvent == VISIBLETIMER)
991 if (nEnsureVisibleCount)
992 EnsureVisible(GetItemCount()-1, false);
993 nEnsureVisibleCount = 0;
997 void CGitProgressList::Sort()
999 if(m_arData.size() < 2)
1001 return;
1004 // We need to sort the blocks which lie between the auxiliary entries
1005 // This is so that any aux data stays where it was
1006 NotificationDataVect::iterator actionBlockBegin;
1007 NotificationDataVect::iterator actionBlockEnd = m_arData.begin(); // We start searching from here
1009 for(;;)
1011 // Search to the start of the non-aux entry in the next block
1012 actionBlockBegin = std::find_if(actionBlockEnd, m_arData.end(), std::not1(std::ptr_fun(&CGitProgressList::NotificationDataIsAux)));
1013 if(actionBlockBegin == m_arData.end())
1015 // There are no more actions
1016 break;
1018 // Now search to find the end of the block
1019 actionBlockEnd = std::find_if(actionBlockBegin+1, m_arData.end(), std::ptr_fun(&CGitProgressList::NotificationDataIsAux));
1020 // Now sort the block
1021 std::sort(actionBlockBegin, actionBlockEnd, &CGitProgressList::SortCompare);
1025 bool CGitProgressList::SortCompare(const NotificationData * pData1, const NotificationData * pData2)
1027 int result = 0;
1028 switch (m_nSortedColumn)
1030 case 0: //action column
1031 result = pData1->sActionColumnText.Compare(pData2->sActionColumnText);
1032 break;
1033 case 1: //path column
1034 // Compare happens after switch()
1035 break;
1036 default:
1037 break;
1040 // Sort by path if everything else is equal
1041 if (result == 0)
1043 result = CTGitPath::Compare(pData1->path, pData2->path);
1046 if (!m_bAscending)
1047 result = -result;
1048 return result < 0;
1051 void CGitProgressList::OnContextMenu(CWnd* pWnd, CPoint point)
1053 if (m_options & ProgOptDryRun)
1054 return; // don't do anything in a dry-run.
1056 if (pWnd == this)
1058 int selIndex = GetSelectionMark();
1059 if ((point.x == -1) && (point.y == -1))
1061 // Menu was invoked from the keyboard rather than by right-clicking
1062 CRect rect;
1063 GetItemRect(selIndex, &rect, LVIR_LABEL);
1064 ClientToScreen(&rect);
1065 point = rect.CenterPoint();
1068 if ((selIndex >= 0)&&(!m_bThreadRunning))
1070 // entry is selected, thread has finished with updating so show the popup menu
1071 CIconMenu popup;
1072 if (popup.CreatePopupMenu())
1074 bool bAdded = false;
1075 NotificationData * data = m_arData[selIndex];
1076 if ((data)&&(!data->path.IsDirectory()))
1079 if (data->action == svn_wc_notify_update_update || data->action == svn_wc_notify_resolved)
1081 if (GetSelectedCount() == 1)
1083 popup.AppendMenuIcon(ID_COMPARE, IDS_LOG_POPUP_COMPARE, IDI_DIFF);
1084 bAdded = true;
1088 if (data->bConflictedActionItem)
1090 if (GetSelectedCount() == 1)
1092 popup.AppendMenuIcon(ID_EDITCONFLICT, IDS_MENUCONFLICT,IDI_CONFLICT);
1093 popup.SetDefaultItem(ID_EDITCONFLICT, FALSE);
1094 popup.AppendMenuIcon(ID_CONFLICTRESOLVE, IDS_SVNPROGRESS_MENUMARKASRESOLVED,IDI_RESOLVE);
1096 popup.AppendMenuIcon(ID_CONFLICTUSETHEIRS, IDS_SVNPROGRESS_MENUUSETHEIRS,IDI_RESOLVE);
1097 popup.AppendMenuIcon(ID_CONFLICTUSEMINE, IDS_SVNPROGRESS_MENUUSEMINE,IDI_RESOLVE);
1099 else if ((data->content_state == svn_wc_notify_state_merged)||(GitProgress_Merge == m_Command)||(data->action == svn_wc_notify_resolved))
1100 popup.SetDefaultItem(ID_COMPARE, FALSE);
1102 if (GetSelectedCount() == 1)
1104 if ((data->action == git_wc_notify_add) ||
1105 (data->action == git_wc_notify_revert) ||
1106 (data->action == git_wc_notify_resolved) ||
1107 (data->action == git_wc_notify_checkout) ||
1108 (data->action == git_wc_notify_update_ref))
1110 popup.AppendMenuIcon(ID_LOG, IDS_MENULOG, IDI_LOG);
1113 if ((data->action == git_wc_notify_add) ||
1114 (data->action == git_wc_notify_revert) ||
1115 (data->action == git_wc_notify_resolved) ||
1116 (data->action == git_wc_notify_checkout))
1118 popup.AppendMenu(MF_SEPARATOR, NULL);
1119 popup.AppendMenuIcon(ID_OPEN, IDS_LOG_POPUP_OPEN, IDI_OPEN);
1120 popup.AppendMenuIcon(ID_OPENWITH, IDS_LOG_POPUP_OPENWITH, IDI_OPEN);
1121 bAdded = true;
1124 } // if ((data)&&(!data->path.IsDirectory()))
1125 if (GetSelectedCount() == 1)
1127 if (data)
1129 CString sPath = GetPathFromColumnText(data->sPathColumnText);
1130 CTGitPath path = CTGitPath(sPath);
1131 if (!sPath.IsEmpty())
1133 if (path.GetDirectory().Exists())
1135 popup.AppendMenuIcon(ID_EXPLORE, IDS_SVNPROGRESS_MENUOPENPARENT, IDI_EXPLORER);
1136 bAdded = true;
1141 if (GetSelectedCount() > 0)
1143 if (bAdded)
1144 popup.AppendMenu(MF_SEPARATOR, NULL);
1145 popup.AppendMenuIcon(ID_COPY, IDS_LOG_POPUP_COPYTOCLIPBOARD,IDI_COPYCLIP);
1146 bAdded = true;
1148 if (bAdded)
1150 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
1151 #if 0//need
1152 DialogEnableWindow(IDOK, FALSE);
1153 #endif
1154 //this->SetPromptApp(&theApp);
1155 theApp.DoWaitCursor(1);
1156 bool bOpenWith = false;
1157 switch (cmd)
1159 case ID_COPY:
1161 CString sLines;
1162 POSITION pos = GetFirstSelectedItemPosition();
1163 while (pos)
1165 int nItem = GetNextSelectedItem(pos);
1166 NotificationData * data = m_arData[nItem];
1167 if (data)
1169 sLines += data->sPathColumnText;
1170 sLines += _T("\r\n");
1173 sLines.TrimRight();
1174 if (!sLines.IsEmpty())
1176 CStringUtils::WriteAsciiStringToClipboard(sLines, GetSafeHwnd());
1179 break;
1180 case ID_EXPLORE:
1182 if (!data)
1183 return;
1184 CString p = GetPathFromColumnText(data->sPathColumnText);
1185 if (PathFileExists(p))
1187 ITEMIDLIST __unaligned * pidl = ILCreateFromPath(p);
1188 if (pidl)
1190 SHOpenFolderAndSelectItems(pidl, 0, 0, 0);
1191 ILFree(pidl);
1193 break;
1195 // if filepath does not exist any more, navigate to closest matching folder
1198 int pos = p.ReverseFind(_T('\\'));
1199 if (pos <= 3)
1200 break;
1201 p = p.Left(pos);
1202 } while (!PathFileExists(p));
1203 ShellExecute(GetSafeHwnd(), _T("explore"), p, nullptr, nullptr, SW_SHOW);
1205 break;
1206 #if 0
1207 case ID_COMPARE:
1209 svn_revnum_t rev = -1;
1210 StringRevMap::iterator it = m_UpdateStartRevMap.end();
1211 if (data->basepath.IsEmpty())
1212 it = m_UpdateStartRevMap.begin();
1213 else
1214 it = m_UpdateStartRevMap.find(data->basepath.GetSVNApiPath(pool));
1215 if (it != m_UpdateStartRevMap.end())
1216 rev = it->second;
1217 // if the file was merged during update, do a three way diff between OLD, MINE, THEIRS
1218 if (data->content_state == svn_wc_notify_state_merged)
1220 CTGitPath basefile = CTempFiles::Instance().GetTempFilePath(false, data->path, rev);
1221 CTGitPath newfile = CTempFiles::Instance().GetTempFilePath(false, data->path, SVNRev::REV_HEAD);
1222 SVN svn;
1223 if (!svn.Cat(data->path, SVNRev(SVNRev::REV_WC), rev, basefile))
1225 CMessageBox::Show(m_hWnd, svn.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1226 DialogEnableWindow(IDOK, TRUE);
1227 break;
1229 // If necessary, convert the line-endings on the file before diffing
1230 if ((DWORD)CRegDWORD(_T("Software\\TortoiseGit\\ConvertBase"), TRUE))
1232 CTGitPath temporaryFile = CTempFiles::Instance().GetTempFilePath(false, data->path, SVNRev::REV_BASE);
1233 if (!svn.Cat(data->path, SVNRev(SVNRev::REV_BASE), SVNRev(SVNRev::REV_BASE), temporaryFile))
1235 temporaryFile.Reset();
1236 break;
1238 else
1240 newfile = temporaryFile;
1244 SetFileAttributes(newfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1245 SetFileAttributes(basefile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1246 CString revname, wcname, basename;
1247 revname.Format(_T("%s Revision %ld"), (LPCTSTR)data->path.GetUIFileOrDirectoryName(), rev);
1248 wcname.Format(IDS_DIFF_WCNAME, (LPCTSTR)data->path.GetUIFileOrDirectoryName());
1249 basename.Format(IDS_DIFF_BASENAME, (LPCTSTR)data->path.GetUIFileOrDirectoryName());
1250 CAppUtils::StartExtMerge(basefile, newfile, data->path, data->path, basename, revname, wcname, CString(), true);
1252 else
1254 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, data->path, rev);
1255 SVN svn;
1256 if (!svn.Cat(data->path, SVNRev(SVNRev::REV_WC), rev, tempfile))
1258 CMessageBox::Show(m_hWnd, svn.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1259 DialogEnableWindow(IDOK, TRUE);
1260 break;
1262 else
1264 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1265 CString revname, wcname;
1266 revname.Format(_T("%s Revision %ld"), (LPCTSTR)data->path.GetUIFileOrDirectoryName(), rev);
1267 wcname.Format(IDS_DIFF_WCNAME, (LPCTSTR)data->path.GetUIFileOrDirectoryName());
1268 CAppUtils::StartExtDiff(
1269 tempfile, data->path, revname, wcname,
1270 CAppUtils::DiffFlags().AlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000)));
1274 break;
1275 case ID_EDITCONFLICT:
1277 CString sPath = GetPathFromColumnText(data->sPathColumnText);
1278 SVNDiff::StartConflictEditor(CTGitPath(sPath));
1280 break;
1281 case ID_CONFLICTUSETHEIRS:
1282 case ID_CONFLICTUSEMINE:
1283 case ID_CONFLICTRESOLVE:
1285 svn_wc_conflict_choice_t result = svn_wc_conflict_choose_merged;
1286 switch (cmd)
1288 case ID_CONFLICTUSETHEIRS:
1289 result = svn_wc_conflict_choose_theirs_full;
1290 break;
1291 case ID_CONFLICTUSEMINE:
1292 result = svn_wc_conflict_choose_mine_full;
1293 break;
1294 case ID_CONFLICTRESOLVE:
1295 result = svn_wc_conflict_choose_merged;
1296 break;
1298 SVN svn;
1299 POSITION pos = GetFirstSelectedItemPosition();
1300 CString sResolvedPaths;
1301 while (pos)
1303 int nItem = GetNextSelectedItem(pos);
1304 NotificationData * data = m_arData[nItem];
1305 if (data)
1307 if (data->bConflictedActionItem)
1309 if (!svn.Resolve(data->path, result, FALSE))
1311 CMessageBox::Show(m_hWnd, svn.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1312 DialogEnableWindow(IDOK, TRUE);
1313 break;
1315 else
1317 data->color = ::GetSysColor(COLOR_WINDOWTEXT);
1318 data->action = svn_wc_notify_resolved;
1319 data->sActionColumnText.LoadString(IDS_SVNACTION_RESOLVE);
1320 data->bConflictedActionItem = false;
1321 m_nConflicts--;
1323 if (m_nConflicts==0)
1325 // When the last conflict is resolved we remove
1326 // the warning which we assume is in the last line.
1327 int nIndex = GetItemCount()-1;
1328 VERIFY(DeleteItem(nIndex));
1330 delete m_arData[nIndex];
1331 m_arData.pop_back();
1333 sResolvedPaths += data->path.GetWinPathString() + _T("\n");
1338 Invalidate();
1339 CString info = BuildInfoString();
1340 SetDlgItemText(IDC_INFOTEXT, info);
1342 if (!sResolvedPaths.IsEmpty())
1344 CString msg;
1345 msg.Format(IDS_SVNPROGRESS_RESOLVED, (LPCTSTR)sResolvedPaths);
1346 CMessageBox::Show(m_hWnd, msg, _T("TortoiseGit"), MB_OK | MB_ICONINFORMATION);
1349 break;
1350 #endif
1351 case ID_LOG:
1353 if (!data)
1354 return;
1355 CString cmd = _T("/command:log");
1356 CString sPath = GetPathFromColumnText(data->sPathColumnText);
1357 if(data->action == git_wc_notify_update_ref)
1359 cmd += _T(" /path:\"") + GetPathFromColumnText(CString()) + _T("\"");
1360 if (!data->m_OldHash.IsEmpty())
1361 cmd += _T(" /startrev:") + data->m_OldHash.ToString();
1362 if (!data->m_NewHash.IsEmpty())
1363 cmd += _T(" /endrev:") + data->m_NewHash.ToString();
1365 else
1366 cmd += _T(" /path:\"") + sPath + _T("\"");
1367 CAppUtils::RunTortoiseGitProc(cmd);
1369 break;
1370 case ID_OPENWITH:
1371 bOpenWith = true;
1372 case ID_OPEN:
1374 if (!data)
1375 return;
1376 int ret = 0;
1377 CString sWinPath = GetPathFromColumnText(data->sPathColumnText);
1378 if (!bOpenWith)
1379 ret = (int)ShellExecute(this->m_hWnd, NULL, (LPCTSTR)sWinPath, NULL, NULL, SW_SHOWNORMAL);
1380 if ((ret <= HINSTANCE_ERROR)||bOpenWith)
1382 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1383 cmd += sWinPath + _T(" ");
1384 CAppUtils::LaunchApplication(cmd, NULL, false);
1388 #if 0 //need
1389 DialogEnableWindow(IDOK, TRUE);
1390 #endif
1391 theApp.DoWaitCursor(-1);
1392 } // if (bAdded)
1398 void CGitProgressList::OnLvnBegindragSvnprogress(NMHDR* , LRESULT *pResult)
1400 //LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1401 #if 0
1402 int selIndex = GetSelectionMark();
1403 if (selIndex < 0)
1404 return;
1406 CDropFiles dropFiles; // class for creating DROPFILES struct
1408 int index;
1409 POSITION pos = GetFirstSelectedItemPosition();
1410 while ( (index = GetNextSelectedItem(pos)) >= 0 )
1412 NotificationData * data = m_arData[index];
1414 if ( data->kind==svn_node_file || data->kind==svn_node_dir )
1416 CString sPath = GetPathFromColumnText(data->sPathColumnText);
1418 dropFiles.AddFile( sPath );
1422 if (!dropFiles.IsEmpty())
1424 dropFiles.CreateStructure();
1426 #endif
1427 *pResult = 0;
1430 void CGitProgressList::OnSize(UINT nType, int cx, int cy)
1432 CListCtrl::OnSize(nType, cx, cy);
1433 if ((nType == SIZE_RESTORED)&&(m_bLastVisible))
1435 if(!m_hWnd)
1436 return;
1438 int count = GetItemCount();
1439 if (count > 0)
1440 EnsureVisible(count-1, false);
1444 //////////////////////////////////////////////////////////////////////////
1445 /// commands
1446 //////////////////////////////////////////////////////////////////////////
1447 bool CGitProgressList::CmdAdd(CString& sWindowTitle, bool& localoperation)
1449 localoperation = true;
1450 SetWindowTitle(IDS_PROGRS_TITLE_ADD, g_Git.CombinePath(m_targetPathList.GetCommonRoot().GetUIPathString()), sWindowTitle);
1451 SetBackgroundImage(IDI_ADD_BKG);
1452 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_ADD)));
1454 m_itemCountTotal = m_targetPathList.GetCount();
1456 if (g_Git.UsingLibGit2(CGit::GIT_CMD_ADD))
1458 CAutoRepository repo(g_Git.GetGitRepository());
1459 if (!repo)
1461 ReportGitError();
1462 return false;
1465 CAutoIndex index;
1466 if (git_repository_index(index.GetPointer(), repo))
1468 ReportGitError();
1469 return false;
1471 if (git_index_read(index, true))
1473 ReportGitError();
1474 return false;
1477 for (m_itemCount = 0; m_itemCount < m_itemCountTotal; ++m_itemCount)
1479 if (git_index_add_bypath(index, CUnicodeUtils::GetMulti(m_targetPathList[m_itemCount].GetGitPathString(), CP_UTF8)))
1481 ReportGitError();
1482 return false;
1484 Notify(m_targetPathList[m_itemCount], git_wc_notify_add);
1486 if (IsCancelled() == TRUE)
1488 ReportUserCanceled();
1489 return false;
1493 if (git_index_write(index))
1495 ReportGitError();
1496 return false;
1499 else
1501 CMassiveGitTask mgt(L"add -f");
1502 if (!mgt.ExecuteWithNotify(&m_targetPathList, m_bCancelled, git_wc_notify_add, this))
1503 return false;
1506 CShellUpdater::Instance().AddPathsForUpdate(m_targetPathList);
1508 return true;
1511 bool CGitProgressList::CmdResolve(CString& sWindowTitle, bool& localoperation)
1514 localoperation = true;
1515 ASSERT(m_targetPathList.GetCount() == 1);
1516 SetWindowTitle(IDS_PROGRS_TITLE_RESOLVE, g_Git.CombinePath(m_targetPathList.GetCommonRoot().GetUIPathString()), sWindowTitle);
1517 SetBackgroundImage(IDI_RESOLVE_BKG);
1519 m_itemCountTotal = m_targetPathList.GetCount();
1520 for (m_itemCount = 0; m_itemCount < m_itemCountTotal; ++m_itemCount)
1522 CString cmd,out,tempmergefile;
1523 cmd.Format(_T("git.exe add -f -- \"%s\""),m_targetPathList[m_itemCount].GetGitPathString());
1524 if (g_Git.Run(cmd, &out, CP_UTF8))
1526 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK|MB_ICONERROR);
1527 m_bErrorsOccurred=true;
1528 return false;
1531 CAppUtils::RemoveTempMergeFile((CTGitPath &)m_targetPathList[m_itemCount]);
1533 Notify(m_targetPathList[m_itemCount], git_wc_notify_resolved);
1536 CShellUpdater::Instance().AddPathsForUpdate(m_targetPathList);
1538 return true;
1541 bool CGitProgressList::CmdRevert(CString& sWindowTitle, bool& localoperation)
1544 localoperation = true;
1545 SetWindowTitle(IDS_PROGRS_TITLE_REVERT, g_Git.CombinePath(m_targetPathList.GetCommonRoot().GetUIPathString()), sWindowTitle);
1546 SetBackgroundImage(IDI_REVERT_BKG);
1548 m_itemCountTotal = 2 * m_selectedPaths.GetCount();
1549 CTGitPathList delList;
1550 for (m_itemCount = 0; m_itemCount < m_selectedPaths.GetCount(); ++m_itemCount)
1552 CTGitPath path;
1553 int action;
1554 path.SetFromWin(g_Git.CombinePath(m_selectedPaths[m_itemCount]));
1555 action = m_selectedPaths[m_itemCount].m_Action;
1556 /* rename file can't delete because it needs original file*/
1557 if((!(action & CTGitPath::LOGACTIONS_ADDED)) &&
1558 (!(action & CTGitPath::LOGACTIONS_REPLACED)))
1559 delList.AddPath(path);
1561 if (DWORD(CRegDWORD(_T("Software\\TortoiseGit\\RevertWithRecycleBin"), TRUE)))
1562 delList.DeleteAllFiles(true);
1564 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_REVERT)));
1565 for (int i = 0; i < m_selectedPaths.GetCount(); ++i)
1567 if(g_Git.Revert(_T("HEAD"), (CTGitPath&)m_selectedPaths[i]))
1569 CMessageBox::Show(NULL,_T("Revert Fail"),_T("TortoiseGit"),MB_OK|MB_ICONERROR);
1570 m_bErrorsOccurred=true;
1571 return false;
1573 Notify(m_selectedPaths[i], git_wc_notify_revert);
1574 ++m_itemCount;
1576 if (IsCancelled() == TRUE)
1578 ReportUserCanceled();
1579 return false;
1583 CShellUpdater::Instance().AddPathsForUpdate(m_selectedPaths);
1585 return true;
1588 bool CGitProgressList::CmdClone(CString& sWindowTitle, bool& /*localoperation*/)
1590 if (!g_Git.UsingLibGit2(CGit::GIT_CMD_CLONE))
1592 // should never run to here
1593 ASSERT(FALSE);
1594 return false;
1596 this->m_TotalBytesTransferred = 0;
1598 SetWindowTitle(IDS_PROGRS_TITLE_CLONE, m_url.GetGitPathString(), sWindowTitle);
1599 SetBackgroundImage(IDI_SWITCH_BKG);
1600 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROG_CLONE)));
1602 if (m_url.IsEmpty() || m_targetPathList.IsEmpty())
1603 return false;
1605 git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
1606 checkout_opts.checkout_strategy = m_bNoCheckout? GIT_CHECKOUT_NONE : GIT_CHECKOUT_SAFE_CREATE;
1607 checkout_opts.progress_cb = CheckoutCallback;
1608 checkout_opts.progress_payload = this;
1610 git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
1611 callbacks.update_tips = RemoteUpdatetipsCallback;
1612 callbacks.sideband_progress = RemoteProgressCallback;
1613 callbacks.completion = RemoteCompletionCallback;
1614 callbacks.transfer_progress = FetchCallback;
1615 callbacks.credentials = CAppUtils::Git2GetUserPassword;
1616 callbacks.payload = this;
1618 CSmartAnimation animate(m_pAnimate);
1619 CAutoRepository cloned_repo;
1621 git_clone_options cloneOpts = GIT_CLONE_OPTIONS_INIT;
1622 cloneOpts.bare = m_bBare;
1623 cloneOpts.repository_cb = [](git_repository** out, const char* path, int bare, void* /*payload*/) -> int {
1624 git_repository_init_options init_options = GIT_REPOSITORY_INIT_OPTIONS_INIT;
1625 init_options.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE;
1626 init_options.flags |= bare ? GIT_REPOSITORY_INIT_BARE : 0;
1628 return git_repository_init_ext(out, path, &init_options);
1631 cloneOpts.remote_cb = [](git_remote** out, git_repository* repo, const char* /*name*/, const char* url, void* payload) -> int {
1632 int error;
1634 CAutoRemote origin;
1635 if ((error = git_remote_create(origin.GetPointer(), repo, (const char*)payload, url)) < 0)
1636 return error;
1638 *out = origin.Detach();
1639 return 0;
1641 cloneOpts.remote_callbacks = callbacks;
1642 CStringA remoteName = m_remote.IsEmpty() ? "origin" : CUnicodeUtils::GetUTF8(m_remote);
1643 cloneOpts.remote_cb_payload = (void*)(const char*)remoteName;
1645 CStringA checkout_branch = CUnicodeUtils::GetUTF8(m_RefSpec);
1646 if (!checkout_branch.IsEmpty())
1647 cloneOpts.checkout_branch = checkout_branch;
1648 cloneOpts.checkout_opts = checkout_opts;
1650 if (git_clone(cloned_repo.GetPointer(), CUnicodeUtils::GetUTF8(m_url.GetGitPathString()), CUnicodeUtils::GetUTF8(m_targetPathList[0].GetWinPathString()), &cloneOpts) < 0)
1651 goto error;
1653 return true;
1655 error:
1656 ReportGitError();
1657 return false;
1659 bool CGitProgressList::CmdSendMail(CString& sWindowTitle, bool& /*localoperation*/)
1661 ASSERT(m_SendMail);
1662 SetWindowTitle(IDS_PROGRS_TITLE_SENDMAIL, g_Git.CombinePath(m_targetPathList.GetCommonRoot().GetUIPathString()), sWindowTitle);
1663 //SetBackgroundImage(IDI_ADD_BKG);
1664 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_SENDMAIL)));
1666 return m_SendMail->Send(m_targetPathList, this) == 0;
1669 bool CGitProgressList::CmdFetch(CString& sWindowTitle, bool& /*localoperation*/)
1671 if (!g_Git.UsingLibGit2(CGit::GIT_CMD_FETCH))
1673 // should never run to here
1674 ASSERT(0);
1675 return false;
1677 this->m_TotalBytesTransferred = 0;
1679 SetWindowTitle(IDS_PROGRS_TITLE_FETCH, g_Git.m_CurrentDir, sWindowTitle);
1680 SetBackgroundImage(IDI_UPDATE_BKG);
1681 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_TITLE_FETCH)) + _T(" ") + m_url.GetGitPathString() + _T(" ") + m_RefSpec);
1683 CStringA url = CUnicodeUtils::GetMulti(m_url.GetGitPathString(), CP_UTF8);
1685 CSmartAnimation animate(m_pAnimate);
1687 CAutoRepository repo(g_Git.GetGitRepository());
1688 if (!repo)
1690 ReportGitError();
1691 return false;
1694 CAutoRemote remote;
1695 // first try with a named remote (e.g. "origin")
1696 if (git_remote_load(remote.GetPointer(), repo, url) < 0)
1698 // retry with repository located at a specific url
1699 if (git_remote_create_anonymous(remote.GetPointer(), repo, url, nullptr) < 0)
1701 ReportGitError();
1702 return false;
1706 git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
1707 callbacks.update_tips = RemoteUpdatetipsCallback;
1708 callbacks.sideband_progress = RemoteProgressCallback;
1709 callbacks.transfer_progress = FetchCallback;
1710 callbacks.completion = RemoteCompletionCallback;
1711 callbacks.credentials = CAppUtils::Git2GetUserPassword;
1712 callbacks.payload = this;
1714 git_remote_set_callbacks(remote, &callbacks);
1715 git_remote_set_autotag(remote, (git_remote_autotag_option_t)m_AutoTag);
1717 if (!m_RefSpec.IsEmpty() && git_remote_add_fetch(remote, CUnicodeUtils::GetUTF8(m_RefSpec)))
1718 goto error;
1720 // Connect to the remote end specifying that we want to fetch
1721 // information from it.
1722 if (git_remote_connect(remote, GIT_DIRECTION_FETCH) < 0)
1723 goto error;
1725 // Download the packfile and index it. This function updates the
1726 // amount of received data and the indexer stats which lets you
1727 // inform the user about progress.
1728 if (git_remote_download(remote) < 0)
1729 goto error;
1731 // Update the references in the remote's namespace to point to the
1732 // right commits. This may be needed even if there was no packfile
1733 // to download, which can happen e.g. when the branches have been
1734 // changed but all the neede objects are available locally.
1735 if (git_remote_update_tips(remote, nullptr, nullptr) < 0)
1736 goto error;
1738 git_remote_disconnect(remote);
1740 return true;
1742 error:
1743 ReportGitError();
1744 return false;
1747 bool CGitProgressList::CmdReset(CString& sWindowTitle, bool& /*localoperation*/)
1749 if (!g_Git.UsingLibGit2(CGit::GIT_CMD_RESET))
1751 // should never run to here
1752 ASSERT(0);
1753 return false;
1756 SetWindowTitle(IDS_PROGRS_TITLE_RESET, g_Git.m_CurrentDir, sWindowTitle);
1757 SetBackgroundImage(IDI_UPDATE_BKG);
1758 int resetTypesResource[] = { IDS_RESET_SOFT, IDS_RESET_MIXED, IDS_RESET_HARD };
1759 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_TITLE_RESET)) + _T(" ") + CString(MAKEINTRESOURCE(resetTypesResource[m_resetType])) + _T(" ") + m_revision);
1761 CAutoRepository repo(g_Git.GetGitRepository());
1762 if (!repo)
1764 ReportGitError();
1765 return false;
1768 CSmartAnimation animate(m_pAnimate);
1769 CAutoObject target;
1770 if (git_revparse_single(target.GetPointer(), repo, CUnicodeUtils::GetUTF8(m_revision)))
1771 goto error;
1772 if (git_reset(repo, target, (git_reset_t)(m_resetType + 1), nullptr, nullptr))
1773 goto error;
1775 return true;
1777 error:
1778 ReportGitError();
1779 return false;
1783 void CGitProgressList::Init()
1785 SetExtendedStyle (LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER);
1787 DeleteAllItems();
1788 int c = ((CHeaderCtrl*)(GetDlgItem(0)))->GetItemCount()-1;
1789 while (c>=0)
1790 DeleteColumn(c--);
1792 CString temp;
1793 temp.LoadString(IDS_PROGRS_ACTION);
1794 InsertColumn(0, temp);
1795 temp.LoadString(IDS_PROGRS_PATH);
1796 InsertColumn(1, temp);
1798 m_pThread = AfxBeginThread(ProgressThreadEntry, this, THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED);
1799 if (m_pThread==NULL)
1801 ReportError(CString(MAKEINTRESOURCE(IDS_ERR_THREADSTARTFAILED)));
1803 else
1805 m_pThread->m_bAutoDelete = FALSE;
1806 m_pThread->ResumeThread();
1809 // Call this early so that the column headings aren't hidden before any
1810 // text gets added.
1811 ResizeColumns();
1813 SetTimer(VISIBLETIMER, 300, NULL);
1817 void CGitProgressList::OnClose()
1819 if (m_bCancelled)
1821 TerminateThread(m_pThread->m_hThread, (DWORD)-1);
1822 InterlockedExchange(&m_bThreadRunning, FALSE);
1824 else
1826 m_bCancelled = TRUE;
1827 return;
1829 CListCtrl::OnClose();
1833 BOOL CGitProgressList::PreTranslateMessage(MSG* pMsg)
1835 if (pMsg->message == WM_KEYDOWN)
1837 if (pMsg->wParam == 'A')
1839 if (GetKeyState(VK_CONTROL)&0x8000)
1841 // Ctrl-A -> select all
1842 SetSelectionMark(0);
1843 for (int i=0; i<GetItemCount(); ++i)
1845 SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
1849 if ((pMsg->wParam == 'C')||(pMsg->wParam == VK_INSERT))
1851 int selIndex = GetSelectionMark();
1852 if (selIndex >= 0)
1854 if (GetKeyState(VK_CONTROL)&0x8000)
1856 //Ctrl-C -> copy to clipboard
1857 CString sClipdata;
1858 POSITION pos = GetFirstSelectedItemPosition();
1859 if (pos != NULL)
1861 while (pos)
1863 int nItem = GetNextSelectedItem(pos);
1864 CString sAction = GetItemText(nItem, 0);
1865 CString sPath = GetItemText(nItem, 1);
1866 CString sMime = GetItemText(nItem, 2);
1867 CString sLogCopyText;
1868 sLogCopyText.Format(_T("%s: %s %s\r\n"),
1869 (LPCTSTR)sAction, (LPCTSTR)sPath, (LPCTSTR)sMime);
1870 sClipdata += sLogCopyText;
1872 CStringUtils::WriteAsciiStringToClipboard(sClipdata);
1877 } // if (pMsg->message == WM_KEYDOWN)
1878 return CListCtrl::PreTranslateMessage(pMsg);
1881 CString CGitProgressList::GetPathFromColumnText(const CString& sColumnText)
1883 CString sPath = sColumnText;
1884 if (sPath.Find(':')<0)
1886 // the path is not absolute: add the common root of all paths to it
1887 sPath = g_Git.CombinePath(sColumnText);
1889 return sPath;
1892 void CGitProgressList::SetWindowTitle(UINT id, const CString& urlorpath, CString& dialogname)
1894 if (!m_bSetTitle || !m_pPostWnd)
1895 return;
1897 dialogname.LoadString(id);
1898 CAppUtils::SetWindowTitle(m_pPostWnd->GetSafeHwnd(), urlorpath, dialogname);