Update libgit2
[TortoiseGit.git] / src / TortoiseProc / GitProgressList.cpp
blob1d113f856ffe7d6046edf3241321bb97a2fc8d3e
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 "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 "LogFile.h"
41 #include "ShellUpdater.h"
42 #include "IconMenu.h"
43 #include "BugTraqAssociations.h"
44 #include "Patch.h"
45 #include "MassiveGitTask.h"
46 #include "SmartHandle.h"
47 #include "LoglistUtils.h"
49 BOOL CGitProgressList::m_bAscending = FALSE;
50 int CGitProgressList::m_nSortedColumn = -1;
52 #define TRANSFERTIMER 100
53 #define VISIBLETIMER 101
54 // CGitProgressList
56 enum GITProgressDlgContextMenuCommands
58 // needs to start with 1, since 0 is the return value if *nothing* is clicked on in the context menu
59 ID_COMPARE = 1,
60 ID_EDITCONFLICT,
61 ID_CONFLICTRESOLVE,
62 ID_CONFLICTUSETHEIRS,
63 ID_CONFLICTUSEMINE,
64 ID_LOG,
65 ID_OPEN,
66 ID_OPENWITH,
67 ID_EXPLORE,
68 ID_COPY
71 IMPLEMENT_DYNAMIC(CGitProgressList, CListCtrl)
73 class CSmartAnimation
75 CAnimateCtrl* m_pAnimate;
77 public:
78 CSmartAnimation(CAnimateCtrl* pAnimate)
80 m_pAnimate = pAnimate;
81 if (m_pAnimate)
83 m_pAnimate->ShowWindow(SW_SHOW);
84 m_pAnimate->Play(0, INT_MAX, INT_MAX);
87 ~CSmartAnimation()
89 if (m_pAnimate)
91 m_pAnimate->Stop();
92 m_pAnimate->ShowWindow(SW_HIDE);
97 CGitProgressList::CGitProgressList():CListCtrl()
98 , m_bCancelled(FALSE)
99 , m_pThread(NULL)
100 , m_bErrorsOccurred(false)
101 , m_bBare(false)
102 , m_bNoCheckout(false)
103 , m_AutoTag(GIT_REMOTE_DOWNLOAD_TAGS_AUTO)
104 , m_resetType(0)
105 , m_options(ProgOptNone)
106 , m_bSetTitle(false)
107 , m_pTaskbarList(nullptr)
108 , m_SendMail(nullptr)
109 , m_Command(GitProgress_none)
110 , m_AlwaysConflicted(false)
111 , m_bThreadRunning(FALSE)
112 , m_keepchangelist(false)
113 , m_nConflicts(0)
114 , m_bMergesAddsDeletesOccurred(FALSE)
115 , iFirstResized(0)
116 , bSecondResized(false)
117 , nEnsureVisibleCount(0)
118 , m_TotalBytesTransferred(0)
119 , m_bFinishedItemAdded(false)
120 , m_bLastVisible(false)
121 , m_itemCount(-1)
122 , m_itemCountTotal(-1)
124 m_pInfoCtrl = nullptr;
125 m_pAnimate = nullptr;
126 m_pProgControl = nullptr;
127 m_pProgressLabelCtrl = nullptr;
128 m_pPostWnd = nullptr;
129 m_columnbuf[0] = 0;
132 CGitProgressList::~CGitProgressList()
134 for (size_t i = 0; i < m_arData.size(); ++i)
136 delete m_arData[i];
138 if(m_pThread != NULL)
140 delete m_pThread;
145 BEGIN_MESSAGE_MAP(CGitProgressList, CListCtrl)
146 ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnNMCustomdrawSvnprogress)
147 ON_NOTIFY_REFLECT(NM_DBLCLK, OnNMDblclkSvnprogress)
148 ON_NOTIFY_REFLECT(HDN_ITEMCLICK, OnHdnItemclickSvnprogress)
149 ON_NOTIFY_REFLECT(LVN_BEGINDRAG, OnLvnBegindragSvnprogress)
150 ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnLvnGetdispinfoSvnprogress)
151 ON_MESSAGE(WM_SHOWCONFLICTRESOLVER, OnShowConflictResolver)
152 ON_WM_SIZE()
153 ON_WM_TIMER()
154 ON_WM_CONTEXTMENU()
155 ON_WM_CLOSE()
156 END_MESSAGE_MAP()
158 void CGitProgressList::Cancel()
160 m_bCancelled = TRUE;
165 // CGitProgressList message handlers
168 LRESULT CGitProgressList::OnShowConflictResolver(WPARAM /*wParam*/, LPARAM /*lParam*/)
170 #if 0
171 CConflictResolveDlg dlg(this);
172 const svn_wc_conflict_description_t *description = (svn_wc_conflict_description_t *)lParam;
173 if (description)
175 dlg.SetConflictDescription(description);
176 if (m_pTaskbarList)
178 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_PAUSED);
180 if (dlg.DoModal() == IDOK)
182 if (dlg.GetResult() == svn_wc_conflict_choose_postpone)
184 // if the result is conflicted and the dialog returned IDOK,
185 // that means we should not ask again in case of a conflict
186 m_AlwaysConflicted = true;
187 ::SendMessage(GetDlgItem(IDC_NONINTERACTIVE)->GetSafeHwnd(), BM_SETCHECK, BST_CHECKED, 0);
190 m_mergedfile = dlg.GetMergedFile();
191 m_bCancelled = dlg.IsCancelled();
192 if (m_pTaskbarList)
193 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_INDETERMINATE);
194 return dlg.GetResult();
197 return svn_wc_conflict_choose_postpone;
198 #endif
199 return 0;
201 #if 0
202 svn_wc_conflict_choice_t CGitProgressList::ConflictResolveCallback(const svn_wc_conflict_description_t *description, CString& mergedfile)
204 // we only bother the user when merging
205 if (((m_Command == GitProgress_Merge)||(m_Command == GitProgress_MergeAll)||(m_Command == GitProgress_MergeReintegrate))&&(!m_AlwaysConflicted)&&(description))
207 // we're in a worker thread here. That means we must not show a dialog from the thread
208 // but let the UI thread do it.
209 // To do that, we send a message to the UI thread and let it show the conflict resolver dialog.
210 LRESULT dlgResult = ::SendMessage(GetSafeHwnd(), WM_SHOWCONFLICTRESOLVER, 0, (LPARAM)description);
211 mergedfile = m_mergedfile;
212 return (svn_wc_conflict_choice_t)dlgResult;
215 return svn_wc_conflict_choose_postpone;
217 #endif
218 void CGitProgressList::AddItemToList()
220 int totalcount = GetItemCount();
222 SetItemCountEx(totalcount+1, LVSICF_NOSCROLL|LVSICF_NOINVALIDATEALL);
223 // make columns width fit
224 if (iFirstResized < 30)
226 // only resize the columns for the first 30 or so entries.
227 // after that, don't resize them anymore because that's an
228 // expensive function call and the columns will be sized
229 // close enough already.
230 ResizeColumns();
231 ++iFirstResized;
234 // Make sure the item is *entirely* visible even if the horizontal
235 // scroll bar is visible.
236 int count = GetCountPerPage();
237 if (totalcount <= (GetTopIndex() + count + nEnsureVisibleCount + 2))
239 ++nEnsureVisibleCount;
240 m_bLastVisible = true;
242 else
244 nEnsureVisibleCount = 0;
245 if (IsIconic() == 0)
246 m_bLastVisible = false;
251 BOOL CGitProgressList::Notify(const CTGitPath& path, git_wc_notify_action_t action
253 svn_node_kind_t kind, const CString& mime_type,
254 svn_wc_notify_state_t content_state,
255 svn_wc_notify_state_t prop_state, LONG rev,
256 const svn_lock_t * lock, svn_wc_notify_lock_state_t lock_state,
257 const CString& changelistname,
258 svn_merge_range_t * range,
259 svn_error_t * err, apr_pool_t * pool
262 bool bNoNotify = false;
263 bool bDoAddData = true;
264 NotificationData * data = new NotificationData();
265 data->path = path;
266 data->action = action;
267 data->sPathColumnText=path.GetGitPathString();
268 data->bAuxItem = false;
270 if (this->m_pAnimate)
271 this->m_pAnimate->ShowWindow(SW_HIDE);
273 #if 0
274 data->kind = kind;
275 data->mime_type = mime_type;
276 data->content_state = content_state;
277 data->prop_state = prop_state;
278 data->rev = rev;
279 data->lock_state = lock_state;
280 data->changelistname = changelistname;
281 if ((lock)&&(lock->owner))
282 data->owner = CUnicodeUtils::GetUnicode(lock->owner);
283 data->sPathColumnText = path.GetUIPathString();
284 if (!m_basePath.IsEmpty())
285 data->basepath = m_basePath;
286 if (range)
287 data->merge_range = *range;
288 #endif
289 switch (data->action)
291 case git_wc_notify_add:
292 //case svn_wc_notify_update_add:
293 // if ((data->content_state == svn_wc_notify_state_conflicted) || (data->prop_state == svn_wc_notify_state_conflicted))
294 // {
295 // data->color = m_Colors.GetColor(CColors::Conflict);
296 // data->bConflictedActionItem = true;
297 // data->sActionColumnText.LoadString(IDS_SVNACTION_CONFLICTED);
298 // ++m_nConflicts;
299 // }
300 // else
301 // {
302 // m_bMergesAddsDeletesOccurred = true;
303 data->sActionColumnText.LoadString(IDS_SVNACTION_ADD);
304 data->color = m_Colors.GetColor(CColors::Added);
305 // }
306 break;
307 case git_wc_notify_sendmail:
308 data->sActionColumnText.LoadString(IDS_SVNACTION_SENDMAIL_START);
309 data->color = m_Colors.GetColor(CColors::Modified);
310 break;
312 case git_wc_notify_resolved:
313 data->sActionColumnText.LoadString(IDS_SVNACTION_RESOLVE);
314 break;
316 case git_wc_notify_revert:
317 data->sActionColumnText.LoadString(IDS_SVNACTION_REVERT);
318 break;
320 case git_wc_notify_checkout:
321 data->sActionColumnText.LoadString(IDS_PROGRS_CMD_CHECKOUT);
322 data->color = m_Colors.GetColor(CColors::Added);
323 data->bAuxItem = false;
324 break;
326 #if 0
327 case svn_wc_notify_commit_added:
328 data->sActionColumnText.LoadString(IDS_SVNACTION_ADDING);
329 data->color = m_Colors.GetColor(CColors::Added);
330 break;
331 case svn_wc_notify_copy:
332 data->sActionColumnText.LoadString(IDS_SVNACTION_COPY);
333 break;
334 case svn_wc_notify_commit_modified:
335 data->sActionColumnText.LoadString(IDS_SVNACTION_MODIFIED);
336 data->color = m_Colors.GetColor(CColors::Modified);
337 break;
338 case svn_wc_notify_delete:
339 case svn_wc_notify_update_delete:
340 data->sActionColumnText.LoadString(IDS_SVNACTION_DELETE);
341 m_bMergesAddsDeletesOccurred = true;
342 data->color = m_Colors.GetColor(CColors::Deleted);
343 break;
344 case svn_wc_notify_commit_deleted:
345 data->sActionColumnText.LoadString(IDS_SVNACTION_DELETING);
346 data->color = m_Colors.GetColor(CColors::Deleted);
347 break;
348 case svn_wc_notify_restore:
349 data->sActionColumnText.LoadString(IDS_SVNACTION_RESTORE);
350 break;
352 case svn_wc_notify_update_replace:
353 case svn_wc_notify_commit_replaced:
354 data->sActionColumnText.LoadString(IDS_SVNACTION_REPLACED);
355 data->color = m_Colors.GetColor(CColors::Deleted);
356 break;
357 case svn_wc_notify_exists:
358 if ((data->content_state == svn_wc_notify_state_conflicted) || (data->prop_state == svn_wc_notify_state_conflicted))
360 data->color = m_Colors.GetColor(CColors::Conflict);
361 data->bConflictedActionItem = true;
362 ++m_nConflicts;
363 data->sActionColumnText.LoadString(IDS_SVNACTION_CONFLICTED);
365 else if ((data->content_state == svn_wc_notify_state_merged) || (data->prop_state == svn_wc_notify_state_merged))
367 data->color = m_Colors.GetColor(CColors::Merged);
368 m_bMergesAddsDeletesOccurred = true;
369 data->sActionColumnText.LoadString(IDS_SVNACTION_MERGED);
371 else
372 data->sActionColumnText.LoadString(IDS_SVNACTION_EXISTS);
373 break;
374 case svn_wc_notify_update_update:
375 // if this is an inoperative dir change, don't show the notification.
376 // an inoperative dir change is when a directory gets updated without
377 // any real change in either text or properties.
378 if ((kind == svn_node_dir)
379 && ((prop_state == svn_wc_notify_state_inapplicable)
380 || (prop_state == svn_wc_notify_state_unknown)
381 || (prop_state == svn_wc_notify_state_unchanged)))
383 bNoNotify = true;
384 break;
386 if ((data->content_state == svn_wc_notify_state_conflicted) || (data->prop_state == svn_wc_notify_state_conflicted))
388 data->color = m_Colors.GetColor(CColors::Conflict);
389 data->bConflictedActionItem = true;
390 ++m_nConflicts;
391 data->sActionColumnText.LoadString(IDS_SVNACTION_CONFLICTED);
393 else if ((data->content_state == svn_wc_notify_state_merged) || (data->prop_state == svn_wc_notify_state_merged))
395 data->color = m_Colors.GetColor(CColors::Merged);
396 m_bMergesAddsDeletesOccurred = true;
397 data->sActionColumnText.LoadString(IDS_SVNACTION_MERGED);
399 else if (((data->content_state != svn_wc_notify_state_unchanged)&&(data->content_state != svn_wc_notify_state_unknown)) ||
400 ((data->prop_state != svn_wc_notify_state_unchanged)&&(data->prop_state != svn_wc_notify_state_unknown)))
402 data->sActionColumnText.LoadString(IDS_SVNACTION_UPDATE);
404 else
406 bNoNotify = true;
407 break;
409 if (lock_state == svn_wc_notify_lock_state_unlocked)
411 CString temp(MAKEINTRESOURCE(IDS_SVNACTION_UNLOCKED));
412 data->sActionColumnText += _T(", ") + temp;
414 break;
416 case svn_wc_notify_update_external:
417 // For some reason we build a list of externals...
418 m_ExtStack.AddHead(path.GetUIPathString());
419 data->sActionColumnText.LoadString(IDS_SVNACTION_EXTERNAL);
420 data->bAuxItem = true;
421 break;
423 case svn_wc_notify_update_completed:
425 data->sActionColumnText.LoadString(IDS_SVNACTION_COMPLETED);
426 data->bAuxItem = true;
427 bool bEmpty = !!m_ExtStack.IsEmpty();
428 if (!bEmpty)
429 data->sPathColumnText.Format(IDS_PROGRS_PATHATREV, (LPCTSTR)m_ExtStack.RemoveHead(), rev);
430 else
431 data->sPathColumnText.Format(IDS_PROGRS_ATREV, rev);
433 if ((m_nConflicts>0)&&(bEmpty))
435 // We're going to add another aux item - let's shove this current onto the list first
436 // I don't really like this, but it will do for the moment.
437 m_arData.push_back(data);
438 AddItemToList();
440 data = new NotificationData();
441 data->bAuxItem = true;
442 data->sActionColumnText.LoadString(IDS_PROGRS_CONFLICTSOCCURED_WARNING);
443 data->sPathColumnText.LoadString(IDS_PROGRS_CONFLICTSOCCURED);
444 data->color = m_Colors.GetColor(CColors::Conflict);
445 CSoundUtils::PlayTSVNWarning();
446 // This item will now be added after the switch statement
448 if (!m_basePath.IsEmpty())
449 m_FinishedRevMap[m_basePath.GetSVNApiPath(pool)] = rev;
450 m_RevisionEnd = rev;
451 m_bFinishedItemAdded = true;
453 break;
454 case svn_wc_notify_commit_postfix_txdelta:
455 data->sActionColumnText.LoadString(IDS_SVNACTION_POSTFIX);
456 break;
457 case svn_wc_notify_failed_revert:
458 data->sActionColumnText.LoadString(IDS_SVNACTION_FAILEDREVERT);
459 break;
460 case svn_wc_notify_status_completed:
461 case svn_wc_notify_status_external:
462 data->sActionColumnText.LoadString(IDS_SVNACTION_STATUS);
463 break;
464 case svn_wc_notify_skip:
465 if ((content_state == svn_wc_notify_state_missing)||(content_state == svn_wc_notify_state_obstructed)||(content_state == svn_wc_notify_state_conflicted))
467 data->sActionColumnText.LoadString(IDS_SVNACTION_SKIPMISSING);
469 // The color settings dialog describes the red color with
470 // "possible or real conflict / obstructed" which also applies to
471 // skipped targets during a merge. So we just use the same color.
472 data->color = m_Colors.GetColor(CColors::Conflict);
474 else
475 data->sActionColumnText.LoadString(IDS_SVNACTION_SKIP);
476 break;
477 case svn_wc_notify_locked:
478 if ((lock)&&(lock->owner))
479 data->sActionColumnText.Format(IDS_SVNACTION_LOCKEDBY, (LPCTSTR)CUnicodeUtils::GetUnicode(lock->owner));
480 break;
481 case svn_wc_notify_unlocked:
482 data->sActionColumnText.LoadString(IDS_SVNACTION_UNLOCKED);
483 break;
484 case svn_wc_notify_failed_lock:
485 data->sActionColumnText.LoadString(IDS_SVNACTION_FAILEDLOCK);
486 m_arData.push_back(data);
487 AddItemToList();
488 ReportError(SVN::GetErrorString(err));
489 bDoAddData = false;
490 if (err->apr_err == SVN_ERR_FS_OUT_OF_DATE)
491 m_bLockWarning = true;
492 if (err->apr_err == SVN_ERR_FS_PATH_ALREADY_LOCKED)
493 m_bLockExists = true;
494 break;
495 case svn_wc_notify_failed_unlock:
496 data->sActionColumnText.LoadString(IDS_SVNACTION_FAILEDUNLOCK);
497 m_arData.push_back(data);
498 AddItemToList();
499 ReportError(SVN::GetErrorString(err));
500 bDoAddData = false;
501 if (err->apr_err == SVN_ERR_FS_OUT_OF_DATE)
502 m_bLockWarning = true;
503 break;
504 case svn_wc_notify_changelist_set:
505 data->sActionColumnText.Format(IDS_SVNACTION_CHANGELISTSET, (LPCTSTR)data->changelistname);
506 break;
507 case svn_wc_notify_changelist_clear:
508 data->sActionColumnText.LoadString(IDS_SVNACTION_CHANGELISTCLEAR);
509 break;
510 case svn_wc_notify_changelist_moved:
511 data->sActionColumnText.Format(IDS_SVNACTION_CHANGELISTMOVED, (LPCTSTR)data->changelistname);
512 break;
513 case svn_wc_notify_foreign_merge_begin:
514 case svn_wc_notify_merge_begin:
515 if (range == NULL)
516 data->sActionColumnText.LoadString(IDS_SVNACTION_MERGEBEGINNONE);
517 else if ((data->merge_range.start == data->merge_range.end) || (data->merge_range.start == data->merge_range.end - 1))
518 data->sActionColumnText.Format(IDS_SVNACTION_MERGEBEGINSINGLE, data->merge_range.end);
519 else if (data->merge_range.start - 1 == data->merge_range.end)
520 data->sActionColumnText.Format(IDS_SVNACTION_MERGEBEGINSINGLEREVERSE, data->merge_range.start);
521 else if (data->merge_range.start < data->merge_range.end)
522 data->sActionColumnText.Format(IDS_SVNACTION_MERGEBEGINMULTIPLE, data->merge_range.start + 1, data->merge_range.end);
523 else
524 data->sActionColumnText.Format(IDS_SVNACTION_MERGEBEGINMULTIPLEREVERSE, data->merge_range.start, data->merge_range.end + 1);
525 data->bAuxItem = true;
526 break;
527 #endif
528 default:
529 break;
530 } // switch (data->action)
532 if (bNoNotify)
533 delete data;
534 else
536 if (bDoAddData)
538 m_arData.push_back(data);
539 AddItemToList();
540 if ((!data->bAuxItem) && (m_itemCount > 0))
542 if (m_pProgControl)
544 m_pProgControl->ShowWindow(SW_SHOW);
545 m_pProgControl->SetPos(m_itemCount);
546 m_pProgControl->SetRange32(0, m_itemCountTotal);
548 if (m_pTaskbarList && m_pPostWnd)
550 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_NORMAL);
551 m_pTaskbarList->SetProgressValue(m_pPostWnd->GetSafeHwnd(), m_itemCount, m_itemCountTotal);
555 //if ((action == svn_wc_notify_commit_postfix_txdelta)&&(bSecondResized == FALSE))
557 // ResizeColumns();
558 // bSecondResized = TRUE;
562 return TRUE;
566 CString CGitProgressList::BuildInfoString()
568 CString infotext;
569 if(this->m_Command == GitProgress_Resolve)
570 infotext = _T("You need commit your change after resolve conflict");
571 #if 0
573 CString temp;
574 int added = 0;
575 int copied = 0;
576 int deleted = 0;
577 int restored = 0;
578 int reverted = 0;
579 int resolved = 0;
580 int conflicted = 0;
581 int updated = 0;
582 int merged = 0;
583 int modified = 0;
584 int skipped = 0;
585 int replaced = 0;
587 for (size_t i=0; i<m_arData.size(); ++i)
589 const NotificationData * dat = m_arData[i];
590 switch (dat->action)
592 case svn_wc_notify_add:
593 case svn_wc_notify_update_add:
594 case svn_wc_notify_commit_added:
595 if (dat->bConflictedActionItem)
596 ++conflicted;
597 else
598 ++added;
599 break;
600 case svn_wc_notify_copy:
601 ++copied;
602 break;
603 case svn_wc_notify_delete:
604 case svn_wc_notify_update_delete:
605 case svn_wc_notify_commit_deleted:
606 ++deleted;
607 break;
608 case svn_wc_notify_restore:
609 ++restored;
610 break;
611 case svn_wc_notify_revert:
612 ++reverted;
613 break;
614 case svn_wc_notify_resolved:
615 ++resolved;
616 break;
617 case svn_wc_notify_update_update:
618 if (dat->bConflictedActionItem)
619 ++conflicted;
620 else if ((dat->content_state == svn_wc_notify_state_merged) || (dat->prop_state == svn_wc_notify_state_merged))
621 ++merged;
622 else
623 ++updated;
624 break;
625 case svn_wc_notify_commit_modified:
626 ++modified;
627 break;
628 case svn_wc_notify_skip:
629 ++skipped;
630 break;
631 case svn_wc_notify_commit_replaced:
632 ++replaced;
633 break;
636 if (conflicted)
638 temp.LoadString(IDS_SVNACTION_CONFLICTED);
639 infotext += temp;
640 temp.Format(_T(":%d "), conflicted);
641 infotext += temp;
643 if (skipped)
645 temp.LoadString(IDS_SVNACTION_SKIP);
646 infotext += temp;
647 infotext.AppendFormat(_T(":%d "), skipped);
649 if (merged)
651 temp.LoadString(IDS_SVNACTION_MERGED);
652 infotext += temp;
653 infotext.AppendFormat(_T(":%d "), merged);
655 if (added)
657 temp.LoadString(IDS_SVNACTION_ADD);
658 infotext += temp;
659 infotext.AppendFormat(_T(":%d "), added);
661 if (deleted)
663 temp.LoadString(IDS_SVNACTION_DELETE);
664 infotext += temp;
665 infotext.AppendFormat(_T(":%d "), deleted);
667 if (modified)
669 temp.LoadString(IDS_SVNACTION_MODIFIED);
670 infotext += temp;
671 infotext.AppendFormat(_T(":%d "), modified);
673 if (copied)
675 temp.LoadString(IDS_SVNACTION_COPY);
676 infotext += temp;
677 infotext.AppendFormat(_T(":%d "), copied);
679 if (replaced)
681 temp.LoadString(IDS_SVNACTION_REPLACED);
682 infotext += temp;
683 infotext.AppendFormat(_T(":%d "), replaced);
685 if (updated)
687 temp.LoadString(IDS_SVNACTION_UPDATE);
688 infotext += temp;
689 infotext.AppendFormat(_T(":%d "), updated);
691 if (restored)
693 temp.LoadString(IDS_SVNACTION_RESTORE);
694 infotext += temp;
695 infotext.AppendFormat(_T(":%d "), restored);
697 if (reverted)
699 temp.LoadString(IDS_SVNACTION_REVERT);
700 infotext += temp;
701 infotext.AppendFormat(_T(":%d "), reverted);
703 if (resolved)
705 temp.LoadString(IDS_SVNACTION_RESOLVE);
706 infotext += temp;
707 infotext.AppendFormat(_T(":%d "), resolved);
709 #endif
710 return infotext;
713 void CGitProgressList::SetSelectedList(const CTGitPathList& selPaths)
715 m_selectedPaths = selPaths;
718 void CGitProgressList::ResizeColumns()
720 SetRedraw(FALSE);
722 TCHAR textbuf[MAX_PATH] = {0};
724 CHeaderCtrl * pHeaderCtrl = (CHeaderCtrl*)(GetDlgItem(0));
725 if (pHeaderCtrl)
727 int maxcol = pHeaderCtrl->GetItemCount()-1;
728 for (int col = 0; col <= maxcol; ++col)
730 // find the longest width of all items
731 int count = GetItemCount();
732 HDITEM hdi = {0};
733 hdi.mask = HDI_TEXT;
734 hdi.pszText = textbuf;
735 hdi.cchTextMax = _countof(textbuf);
736 pHeaderCtrl->GetItem(col, &hdi);
737 int cx = GetStringWidth(hdi.pszText)+20; // 20 pixels for col separator and margin
739 for (int index = 0; index<count; ++index)
741 // get the width of the string and add 12 pixels for the column separator and margins
742 int linewidth = cx;
743 switch (col)
745 case 0:
746 linewidth = GetStringWidth(m_arData[index]->sActionColumnText) + 12;
747 break;
748 case 1:
749 linewidth = GetStringWidth(m_arData[index]->sPathColumnText) + 12;
750 break;
752 if (cx < linewidth)
753 cx = linewidth;
755 SetColumnWidth(col, cx);
759 SetRedraw(TRUE);
762 bool CGitProgressList::SetBackgroundImage(UINT nID)
764 return CAppUtils::SetListCtrlBackgroundImage(GetSafeHwnd(), nID);
767 void CGitProgressList::ReportGitError()
769 ReportError(CGit::GetLibGit2LastErr());
772 void CGitProgressList::ReportUserCanceled()
774 ReportError(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED)));
777 void CGitProgressList::ReportError(const CString& sError)
779 CSoundUtils::PlayTGitError();
780 ReportString(sError, CString(MAKEINTRESOURCE(IDS_ERR_ERROR)), m_Colors.GetColor(CColors::Conflict));
781 m_bErrorsOccurred = true;
784 void CGitProgressList::ReportWarning(const CString& sWarning)
786 CSoundUtils::PlayTGitWarning();
787 ReportString(sWarning, CString(MAKEINTRESOURCE(IDS_WARN_WARNING)), m_Colors.GetColor(CColors::Conflict));
790 void CGitProgressList::ReportNotification(const CString& sNotification)
792 CSoundUtils::PlayTGitNotification();
793 ReportString(sNotification, CString(MAKEINTRESOURCE(IDS_WARN_NOTE)));
796 void CGitProgressList::ReportCmd(const CString& sCmd)
798 ReportString(sCmd, CString(MAKEINTRESOURCE(IDS_PROGRS_CMDINFO)), m_Colors.GetColor(CColors::Cmd));
801 void CGitProgressList::ReportString(CString sMessage, const CString& sMsgKind, COLORREF color)
803 // instead of showing a dialog box with the error message or notification,
804 // just insert the error text into the list control.
805 // that way the user isn't 'interrupted' by a dialog box popping up!
807 // the message may be split up into different lines
808 // so add a new entry for each line of the message
809 while (!sMessage.IsEmpty())
811 NotificationData * data = new NotificationData();
812 data->bAuxItem = true;
813 data->sActionColumnText = sMsgKind;
814 if (sMessage.Find('\n')>=0)
815 data->sPathColumnText = sMessage.Left(sMessage.Find('\n'));
816 else
817 data->sPathColumnText = sMessage;
818 data->sPathColumnText.Trim(_T("\n\r"));
819 data->color = color;
820 if (sMessage.Find('\n')>=0)
822 sMessage = sMessage.Mid(sMessage.Find('\n'));
823 sMessage.Trim(_T("\n\r"));
825 else
826 sMessage.Empty();
827 m_arData.push_back(data);
828 AddItemToList();
832 UINT CGitProgressList::ProgressThreadEntry(LPVOID pVoid)
834 return ((CGitProgressList*)pVoid)->ProgressThread();
837 UINT CGitProgressList::ProgressThread()
839 // The SetParams function should have loaded something for us
841 CString temp;
842 CString sWindowTitle;
843 bool localoperation = false;
844 bool bSuccess = false;
845 m_AlwaysConflicted = false;
847 if(m_pPostWnd)
848 m_pPostWnd->PostMessage(WM_PROG_CMD_START, m_Command);
850 if(m_pProgressLabelCtrl)
852 m_pProgressLabelCtrl->ShowWindow(SW_SHOW);
853 m_pProgressLabelCtrl->SetWindowText(_T(""));
856 // SetAndClearProgressInfo(m_hWnd);
857 m_itemCount = m_itemCountTotal;
859 InterlockedExchange(&m_bThreadRunning, TRUE);
860 iFirstResized = 0;
861 bSecondResized = FALSE;
862 m_bFinishedItemAdded = false;
863 DWORD startTime = GetCurrentTime();
865 if (m_pTaskbarList && m_pPostWnd)
866 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_INDETERMINATE);
868 switch (m_Command)
870 case GitProgress_Add:
871 bSuccess = CmdAdd(sWindowTitle, localoperation);
872 break;
873 case GitProgress_Copy:
874 bSuccess = CmdCopy(sWindowTitle, localoperation);
875 break;
876 case GitProgress_Export:
877 bSuccess = CmdExport(sWindowTitle, localoperation);
878 break;
879 case GitProgress_Rename:
880 bSuccess = CmdRename(sWindowTitle, localoperation);
881 break;
882 case GitProgress_Resolve:
883 bSuccess = CmdResolve(sWindowTitle, localoperation);
884 break;
885 case GitProgress_Revert:
886 bSuccess = CmdRevert(sWindowTitle, localoperation);
887 break;
888 case GitProgress_Switch:
889 bSuccess = CmdSwitch(sWindowTitle, localoperation);
890 break;
891 case GitProgress_SendMail:
892 bSuccess = CmdSendMail(sWindowTitle, localoperation);
893 break;
894 case GitProgress_Clone:
895 bSuccess = CmdClone(sWindowTitle, localoperation);
896 break;
897 case GitProgress_Fetch:
898 bSuccess = CmdFetch(sWindowTitle, localoperation);
899 break;
900 case GitProgress_Reset:
901 bSuccess = CmdReset(sWindowTitle, localoperation);
902 break;
904 if (!bSuccess)
905 temp.LoadString(IDS_PROGRS_TITLEFAILED);
906 else
907 temp.LoadString(IDS_PROGRS_TITLEFIN);
908 sWindowTitle = sWindowTitle + _T(" ") + temp;
909 if (m_bSetTitle && m_pPostWnd)
910 ::SetWindowText(m_pPostWnd->GetSafeHwnd(), sWindowTitle);
912 KillTimer(TRANSFERTIMER);
913 KillTimer(VISIBLETIMER);
915 if (m_pTaskbarList && m_pPostWnd)
917 if (DidErrorsOccur())
919 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_ERROR);
920 m_pTaskbarList->SetProgressValue(m_pPostWnd->GetSafeHwnd(), 100, 100);
922 else
923 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_NOPROGRESS);
926 CString info = BuildInfoString();
927 if (!bSuccess)
928 info.LoadString(IDS_PROGRS_INFOFAILED);
929 if (m_pInfoCtrl)
930 m_pInfoCtrl->SetWindowText(info);
932 ResizeColumns();
934 DWORD time = GetCurrentTime() - startTime;
936 CString sFinalInfo;
937 if (!m_sTotalBytesTransferred.IsEmpty())
939 temp.Format(IDS_PROGRS_TIME, (time / 1000) / 60, (time / 1000) % 60);
940 sFinalInfo.Format(IDS_PROGRS_FINALINFO, m_sTotalBytesTransferred, (LPCTSTR)temp);
941 if (m_pProgressLabelCtrl)
942 m_pProgressLabelCtrl->SetWindowText(sFinalInfo);
944 else
946 if (m_pProgressLabelCtrl)
947 m_pProgressLabelCtrl->ShowWindow(SW_HIDE);
950 if (m_pProgControl)
951 m_pProgControl->ShowWindow(SW_HIDE);
953 if (!m_bFinishedItemAdded)
955 CString log, str;
956 if (bSuccess)
957 str.LoadString(IDS_SUCCESS);
958 else
959 str.LoadString(IDS_FAIL);
960 log.Format(_T("%s (%lu ms @ %s)"), str, time, CLoglistUtils::FormatDateAndTime(CTime::GetCurrentTime(), DATE_SHORTDATE, true, false));
962 // there's no "finished: xxx" line at the end. We add one here to make
963 // sure the user sees that the command is actually finished.
964 ReportString(log, CString(MAKEINTRESOURCE(IDS_PROGRS_FINISHED)), bSuccess? RGB(0,0,255) : RGB(255,0,0));
967 int count = GetItemCount();
968 if ((count > 0)&&(m_bLastVisible))
969 EnsureVisible(count-1, FALSE);
971 CLogFile logfile(g_Git.m_CurrentDir);
972 if (logfile.Open())
974 logfile.AddTimeLine();
975 for (size_t i = 0; i < m_arData.size(); ++i)
977 NotificationData * data = m_arData[i];
978 temp.Format(_T("%-20s : %s"), (LPCTSTR)data->sActionColumnText, (LPCTSTR)data->sPathColumnText);
979 logfile.AddLine(temp);
981 if (!sFinalInfo.IsEmpty())
982 logfile.AddLine(sFinalInfo);
983 logfile.Close();
986 m_bCancelled = TRUE;
987 InterlockedExchange(&m_bThreadRunning, FALSE);
988 #if 0 //need
989 RefreshCursor();
990 #endif
992 if (m_pPostWnd)
993 m_pPostWnd->PostMessage(WM_PROG_CMD_FINISH, this->m_Command, 0L);
995 #if 0
996 DWORD dwAutoClose = CRegStdDWORD(_T("Software\\TortoiseGit\\AutoClose"), CLOSE_MANUAL);
997 if (m_options & ProgOptDryRun)
998 dwAutoClose = 0; // dry run means progress dialog doesn't auto close at all
999 if (!m_bLastVisible)
1000 dwAutoClose = 0;
1001 if (m_dwCloseOnEnd != (DWORD)-1)
1002 dwAutoClose = m_dwCloseOnEnd; // command line value has priority over setting value
1003 if ((dwAutoClose == CLOSE_NOERRORS)&&(!m_bErrorsOccurred))
1004 PostMessage(WM_COMMAND, 1, (LPARAM)GetDlgItem(IDOK)->m_hWnd);
1005 if ((dwAutoClose == CLOSE_NOCONFLICTS)&&(!m_bErrorsOccurred)&&(m_nConflicts==0))
1006 PostMessage(WM_COMMAND, 1, (LPARAM)GetDlgItem(IDOK)->m_hWnd);
1007 if ((dwAutoClose == CLOSE_NOMERGES)&&(!m_bErrorsOccurred)&&(m_nConflicts==0)&&(!m_bMergesAddsDeletesOccurred))
1008 PostMessage(WM_COMMAND, 1, (LPARAM)GetDlgItem(IDOK)->m_hWnd);
1009 if ((dwAutoClose == CLOSE_LOCAL)&&(!m_bErrorsOccurred)&&(m_nConflicts==0)&&(localoperation))
1010 PostMessage(WM_COMMAND, 1, (LPARAM)GetDlgItem(IDOK)->m_hWnd);
1011 #endif
1013 //Don't do anything here which might cause messages to be sent to the window
1014 //The window thread is probably now blocked in OnOK if we've done an auto close
1015 return 0;
1018 void CGitProgressList::OnLvnGetdispinfoSvnprogress(NMHDR *pNMHDR, LRESULT *pResult)
1020 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1022 if (pDispInfo)
1024 if (pDispInfo->item.mask & LVIF_TEXT)
1026 if (pDispInfo->item.iItem < (int)m_arData.size())
1028 const NotificationData * data = m_arData[pDispInfo->item.iItem];
1029 switch (pDispInfo->item.iSubItem)
1031 case 0:
1032 lstrcpyn(m_columnbuf, data->sActionColumnText, MAX_PATH);
1033 break;
1034 case 1:
1035 lstrcpyn(m_columnbuf, data->sPathColumnText, pDispInfo->item.cchTextMax);
1036 if (!data->bAuxItem)
1038 int cWidth = GetColumnWidth(1);
1039 cWidth = max(12, cWidth-12);
1040 CDC * pDC = GetDC();
1041 if (pDC != NULL)
1043 CFont * pFont = pDC->SelectObject(GetFont());
1044 PathCompactPath(pDC->GetSafeHdc(), m_columnbuf, cWidth);
1045 pDC->SelectObject(pFont);
1046 ReleaseDC(pDC);
1049 break;
1050 default:
1051 m_columnbuf[0] = 0;
1053 pDispInfo->item.pszText = m_columnbuf;
1057 *pResult = 0;
1060 void CGitProgressList::OnNMCustomdrawSvnprogress(NMHDR *pNMHDR, LRESULT *pResult)
1062 NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );
1064 // Take the default processing unless we set this to something else below.
1065 *pResult = CDRF_DODEFAULT;
1067 // First thing - check the draw stage. If it's the control's prepaint
1068 // stage, then tell Windows we want messages for every item.
1070 if ( CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage )
1072 *pResult = CDRF_NOTIFYITEMDRAW;
1074 else if ( CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage )
1076 // This is the prepaint stage for an item. Here's where we set the
1077 // item's text color. Our return value will tell Windows to draw the
1078 // item itself, but it will use the new color we set here.
1080 // Tell Windows to paint the control itself.
1081 *pResult = CDRF_DODEFAULT;
1083 ASSERT(pLVCD->nmcd.dwItemSpec < m_arData.size());
1084 if(pLVCD->nmcd.dwItemSpec >= m_arData.size())
1086 return;
1088 const NotificationData * data = m_arData[pLVCD->nmcd.dwItemSpec];
1089 ASSERT(data != NULL);
1090 if (data == NULL)
1091 return;
1093 // Store the color back in the NMLVCUSTOMDRAW struct.
1094 pLVCD->clrText = data->color;
1098 void CGitProgressList::OnNMDblclkSvnprogress(NMHDR * /*pNMHDR*/, LRESULT * /*pResult*/)
1100 #if 0
1101 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1102 *pResult = 0;
1103 if (pNMLV->iItem < 0)
1104 return;
1105 if (m_options & ProgOptDryRun)
1106 return; //don't do anything in a dry-run.
1108 const NotificationData * data = m_arData[pNMLV->iItem];
1109 if (data == NULL)
1110 return;
1112 if (data->bConflictedActionItem)
1114 // We've double-clicked on a conflicted item - do a three-way merge on it
1115 SVNDiff::StartConflictEditor(data->path);
1117 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))
1119 // This is a modified file which has been merged on update. Diff it against base
1120 CTGitPath temporaryFile;
1121 SVNDiff diff(this, this->m_hWnd, true);
1122 diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1123 svn_revnum_t baseRev = 0;
1124 diff.DiffFileAgainstBase(data->path, baseRev);
1126 else if ((!data->bAuxItem)&&(data->path.Exists())&&(!data->path.IsDirectory()))
1128 bool bOpenWith = false;
1129 int ret = (int)ShellExecute(m_hWnd, NULL, data->path.GetWinPath(), NULL, NULL, SW_SHOWNORMAL);
1130 if (ret <= HINSTANCE_ERROR)
1131 bOpenWith = true;
1132 if (bOpenWith)
1134 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1135 cmd += data->path.GetWinPathString() + _T(" ");
1136 CAppUtils::LaunchApplication(cmd, NULL, false);
1139 #endif
1142 void CGitProgressList::OnHdnItemclickSvnprogress(NMHDR *pNMHDR, LRESULT *pResult)
1144 LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
1145 if (m_bThreadRunning)
1146 return;
1147 if (m_nSortedColumn == phdr->iItem)
1148 m_bAscending = !m_bAscending;
1149 else
1150 m_bAscending = TRUE;
1151 m_nSortedColumn = phdr->iItem;
1152 Sort();
1154 CString temp;
1155 SetRedraw(FALSE);
1156 DeleteAllItems();
1157 SetItemCountEx (static_cast<int>(m_arData.size()));
1159 SetRedraw(TRUE);
1161 *pResult = 0;
1164 bool CGitProgressList::NotificationDataIsAux(const NotificationData* pData)
1166 return pData->bAuxItem;
1168 BOOL CGitProgressList::Notify(const git_wc_notify_action_t action, CString str, const git_oid *a, const git_oid *b)
1170 NotificationData * data = new NotificationData();
1171 data->action = action;
1172 data->bAuxItem = false;
1174 if (action == git_wc_notify_update_ref)
1176 data->m_NewHash = b->id;
1177 data->m_OldHash = a->id;
1178 data->sActionColumnText.LoadString(IDS_GITACTION_UPDATE_REF);
1179 data->sPathColumnText.Format(_T("%s\t %s -> %s"), str,
1180 data->m_OldHash.ToString().Left(g_Git.GetShortHASHLength()),
1181 data->m_NewHash.ToString().Left(g_Git.GetShortHASHLength()));
1185 m_arData.push_back(data);
1186 AddItemToList();
1188 if (m_pAnimate)
1189 m_pAnimate->Stop();
1190 if (m_pAnimate)
1191 m_pAnimate->ShowWindow(SW_HIDE);
1192 return TRUE;
1194 BOOL CGitProgressList::Notify(const git_wc_notify_action_t /*action*/, const git_transfer_progress *stat)
1196 static unsigned int start = 0;
1197 unsigned int dt = GetCurrentTime() - start;
1198 double speed = 0;
1200 if (m_bCancelled)
1201 return FALSE;
1203 if (dt > 100)
1205 start = GetCurrentTime();
1206 size_t ds = stat->received_bytes - m_TotalBytesTransferred;
1207 speed = ds * 1000.0/dt;
1208 m_TotalBytesTransferred = stat->received_bytes;
1210 else
1212 return TRUE;
1215 int progress;
1216 progress = stat->received_objects + stat->indexed_objects;
1218 if ((stat->total_objects > 1000) && m_pProgControl && (!m_pProgControl->IsWindowVisible()))
1220 if (m_pProgControl)
1221 m_pProgControl->ShowWindow(SW_SHOW);
1224 if (m_pProgressLabelCtrl && m_pProgressLabelCtrl->IsWindowVisible())
1225 m_pProgressLabelCtrl->ShowWindow(SW_SHOW);
1227 if (m_pProgControl)
1229 m_pProgControl->SetPos(progress);
1230 m_pProgControl->SetRange32(0, 2 * stat->total_objects);
1232 if (m_pTaskbarList && m_pPostWnd)
1234 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_NORMAL);
1235 m_pTaskbarList->SetProgressValue(m_pPostWnd->GetSafeHwnd(), progress, 2 * stat->total_objects);
1238 CString progText;
1239 if (stat->received_bytes < 1024)
1240 m_sTotalBytesTransferred.Format(IDS_SVN_PROGRESS_TOTALBYTESTRANSFERRED, (int64_t)stat->received_bytes);
1241 else if (stat->received_bytes < 1200000)
1242 m_sTotalBytesTransferred.Format(IDS_SVN_PROGRESS_TOTALTRANSFERRED, (int64_t)stat->received_bytes / 1024);
1243 else
1244 m_sTotalBytesTransferred.Format(IDS_SVN_PROGRESS_TOTALMBTRANSFERRED, (double)((double)stat->received_bytes / 1024000.0));
1246 CString str;
1247 if(speed < 1024)
1248 str.Format(_T("%fB/s"), speed);
1249 else if(speed < 1024 * 1024)
1250 str.Format(_T("%.2fKB/s"), speed / 1024);
1251 else
1252 str.Format(_T("%.2fMB/s"), speed / 1024000.0);
1254 progText.Format(IDS_SVN_PROGRESS_TOTALANDSPEED, (LPCTSTR)m_sTotalBytesTransferred, (LPCTSTR)str);
1255 if (m_pProgressLabelCtrl)
1256 m_pProgressLabelCtrl->SetWindowText(progText);
1258 return TRUE;
1261 void CGitProgressList::OnTimer(UINT_PTR nIDEvent)
1263 if (nIDEvent == TRANSFERTIMER)
1265 CString progText;
1266 CString progSpeed;
1267 progSpeed.Format(IDS_SVN_PROGRESS_BYTES_SEC, 0);
1268 progText.Format(IDS_SVN_PROGRESS_TOTALANDSPEED, (LPCTSTR)m_sTotalBytesTransferred, (LPCTSTR)progSpeed);
1269 if (m_pProgressLabelCtrl)
1270 m_pProgressLabelCtrl->SetWindowText(progText);
1272 KillTimer(TRANSFERTIMER);
1274 if (nIDEvent == VISIBLETIMER)
1276 if (nEnsureVisibleCount)
1277 EnsureVisible(GetItemCount()-1, false);
1278 nEnsureVisibleCount = 0;
1282 void CGitProgressList::Sort()
1284 if(m_arData.size() < 2)
1286 return;
1289 // We need to sort the blocks which lie between the auxiliary entries
1290 // This is so that any aux data stays where it was
1291 NotificationDataVect::iterator actionBlockBegin;
1292 NotificationDataVect::iterator actionBlockEnd = m_arData.begin(); // We start searching from here
1294 for(;;)
1296 // Search to the start of the non-aux entry in the next block
1297 actionBlockBegin = std::find_if(actionBlockEnd, m_arData.end(), std::not1(std::ptr_fun(&CGitProgressList::NotificationDataIsAux)));
1298 if(actionBlockBegin == m_arData.end())
1300 // There are no more actions
1301 break;
1303 // Now search to find the end of the block
1304 actionBlockEnd = std::find_if(actionBlockBegin+1, m_arData.end(), std::ptr_fun(&CGitProgressList::NotificationDataIsAux));
1305 // Now sort the block
1306 std::sort(actionBlockBegin, actionBlockEnd, &CGitProgressList::SortCompare);
1310 bool CGitProgressList::SortCompare(const NotificationData * pData1, const NotificationData * pData2)
1312 int result = 0;
1313 switch (m_nSortedColumn)
1315 case 0: //action column
1316 result = pData1->sActionColumnText.Compare(pData2->sActionColumnText);
1317 break;
1318 case 1: //path column
1319 // Compare happens after switch()
1320 break;
1321 default:
1322 break;
1325 // Sort by path if everything else is equal
1326 if (result == 0)
1328 result = CTGitPath::Compare(pData1->path, pData2->path);
1331 if (!m_bAscending)
1332 result = -result;
1333 return result < 0;
1336 void CGitProgressList::OnContextMenu(CWnd* pWnd, CPoint point)
1338 if (m_options & ProgOptDryRun)
1339 return; // don't do anything in a dry-run.
1341 if (pWnd == this)
1343 int selIndex = GetSelectionMark();
1344 if ((point.x == -1) && (point.y == -1))
1346 // Menu was invoked from the keyboard rather than by right-clicking
1347 CRect rect;
1348 GetItemRect(selIndex, &rect, LVIR_LABEL);
1349 ClientToScreen(&rect);
1350 point = rect.CenterPoint();
1353 if ((selIndex >= 0)&&(!m_bThreadRunning))
1355 // entry is selected, thread has finished with updating so show the popup menu
1356 CIconMenu popup;
1357 if (popup.CreatePopupMenu())
1359 bool bAdded = false;
1360 NotificationData * data = m_arData[selIndex];
1361 if ((data)&&(!data->path.IsDirectory()))
1364 if (data->action == svn_wc_notify_update_update || data->action == svn_wc_notify_resolved)
1366 if (GetSelectedCount() == 1)
1368 popup.AppendMenuIcon(ID_COMPARE, IDS_LOG_POPUP_COMPARE, IDI_DIFF);
1369 bAdded = true;
1373 if (data->bConflictedActionItem)
1375 if (GetSelectedCount() == 1)
1377 popup.AppendMenuIcon(ID_EDITCONFLICT, IDS_MENUCONFLICT,IDI_CONFLICT);
1378 popup.SetDefaultItem(ID_EDITCONFLICT, FALSE);
1379 popup.AppendMenuIcon(ID_CONFLICTRESOLVE, IDS_SVNPROGRESS_MENUMARKASRESOLVED,IDI_RESOLVE);
1381 popup.AppendMenuIcon(ID_CONFLICTUSETHEIRS, IDS_SVNPROGRESS_MENUUSETHEIRS,IDI_RESOLVE);
1382 popup.AppendMenuIcon(ID_CONFLICTUSEMINE, IDS_SVNPROGRESS_MENUUSEMINE,IDI_RESOLVE);
1384 else if ((data->content_state == svn_wc_notify_state_merged)||(GitProgress_Merge == m_Command)||(data->action == svn_wc_notify_resolved))
1385 popup.SetDefaultItem(ID_COMPARE, FALSE);
1387 if (GetSelectedCount() == 1)
1389 if ((data->action == git_wc_notify_add)||
1390 (data->action == git_wc_notify_revert)||
1391 (data->action == git_wc_notify_resolved)||
1392 (data->action == git_wc_notify_checkout)||
1393 (data->action == git_wc_notify_update_ref))
1395 popup.AppendMenuIcon(ID_LOG, IDS_MENULOG,IDI_LOG);
1396 popup.AppendMenu(MF_SEPARATOR, NULL);
1397 popup.AppendMenuIcon(ID_OPEN, IDS_LOG_POPUP_OPEN, IDI_OPEN);
1398 popup.AppendMenuIcon(ID_OPENWITH, IDS_LOG_POPUP_OPENWITH, IDI_OPEN);
1399 bAdded = true;
1402 } // if ((data)&&(!data->path.IsDirectory()))
1403 if (GetSelectedCount() == 1)
1405 if (data)
1407 CString sPath = GetPathFromColumnText(data->sPathColumnText);
1408 CTGitPath path = CTGitPath(sPath);
1409 if (!sPath.IsEmpty())
1411 if (path.GetDirectory().Exists())
1413 popup.AppendMenuIcon(ID_EXPLORE, IDS_SVNPROGRESS_MENUOPENPARENT, IDI_EXPLORER);
1414 bAdded = true;
1419 if (GetSelectedCount() > 0)
1421 if (bAdded)
1422 popup.AppendMenu(MF_SEPARATOR, NULL);
1423 popup.AppendMenuIcon(ID_COPY, IDS_LOG_POPUP_COPYTOCLIPBOARD,IDI_COPYCLIP);
1424 bAdded = true;
1426 if (bAdded)
1428 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
1429 #if 0//need
1430 DialogEnableWindow(IDOK, FALSE);
1431 #endif
1432 //this->SetPromptApp(&theApp);
1433 theApp.DoWaitCursor(1);
1434 bool bOpenWith = false;
1435 switch (cmd)
1437 case ID_COPY:
1439 CString sLines;
1440 POSITION pos = GetFirstSelectedItemPosition();
1441 while (pos)
1443 int nItem = GetNextSelectedItem(pos);
1444 NotificationData * data = m_arData[nItem];
1445 if (data)
1447 sLines += data->sPathColumnText;
1448 sLines += _T("\r\n");
1451 sLines.TrimRight();
1452 if (!sLines.IsEmpty())
1454 CStringUtils::WriteAsciiStringToClipboard(sLines, GetSafeHwnd());
1457 break;
1458 case ID_EXPLORE:
1460 if (!data)
1461 return;
1462 CString p = GetPathFromColumnText(data->sPathColumnText);
1463 if (PathFileExists(p))
1465 ITEMIDLIST __unaligned * pidl = ILCreateFromPath(p);
1466 if (pidl)
1468 SHOpenFolderAndSelectItems(pidl, 0, 0, 0);
1469 ILFree(pidl);
1471 break;
1473 // if filepath does not exist any more, navigate to closest matching folder
1476 int pos = p.ReverseFind(_T('\\'));
1477 if (pos <= 3)
1478 break;
1479 p = p.Left(pos);
1480 } while (!PathFileExists(p));
1481 ShellExecute(GetSafeHwnd(), _T("explore"), p, nullptr, nullptr, SW_SHOW);
1483 break;
1484 #if 0
1485 case ID_COMPARE:
1487 svn_revnum_t rev = -1;
1488 StringRevMap::iterator it = m_UpdateStartRevMap.end();
1489 if (data->basepath.IsEmpty())
1490 it = m_UpdateStartRevMap.begin();
1491 else
1492 it = m_UpdateStartRevMap.find(data->basepath.GetSVNApiPath(pool));
1493 if (it != m_UpdateStartRevMap.end())
1494 rev = it->second;
1495 // if the file was merged during update, do a three way diff between OLD, MINE, THEIRS
1496 if (data->content_state == svn_wc_notify_state_merged)
1498 CTGitPath basefile = CTempFiles::Instance().GetTempFilePath(false, data->path, rev);
1499 CTGitPath newfile = CTempFiles::Instance().GetTempFilePath(false, data->path, SVNRev::REV_HEAD);
1500 SVN svn;
1501 if (!svn.Cat(data->path, SVNRev(SVNRev::REV_WC), rev, basefile))
1503 CMessageBox::Show(m_hWnd, svn.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1504 DialogEnableWindow(IDOK, TRUE);
1505 break;
1507 // If necessary, convert the line-endings on the file before diffing
1508 if ((DWORD)CRegDWORD(_T("Software\\TortoiseGit\\ConvertBase"), TRUE))
1510 CTGitPath temporaryFile = CTempFiles::Instance().GetTempFilePath(false, data->path, SVNRev::REV_BASE);
1511 if (!svn.Cat(data->path, SVNRev(SVNRev::REV_BASE), SVNRev(SVNRev::REV_BASE), temporaryFile))
1513 temporaryFile.Reset();
1514 break;
1516 else
1518 newfile = temporaryFile;
1522 SetFileAttributes(newfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1523 SetFileAttributes(basefile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1524 CString revname, wcname, basename;
1525 revname.Format(_T("%s Revision %ld"), (LPCTSTR)data->path.GetUIFileOrDirectoryName(), rev);
1526 wcname.Format(IDS_DIFF_WCNAME, (LPCTSTR)data->path.GetUIFileOrDirectoryName());
1527 basename.Format(IDS_DIFF_BASENAME, (LPCTSTR)data->path.GetUIFileOrDirectoryName());
1528 CAppUtils::StartExtMerge(basefile, newfile, data->path, data->path, basename, revname, wcname, CString(), true);
1530 else
1532 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, data->path, rev);
1533 SVN svn;
1534 if (!svn.Cat(data->path, SVNRev(SVNRev::REV_WC), rev, tempfile))
1536 CMessageBox::Show(m_hWnd, svn.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1537 DialogEnableWindow(IDOK, TRUE);
1538 break;
1540 else
1542 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1543 CString revname, wcname;
1544 revname.Format(_T("%s Revision %ld"), (LPCTSTR)data->path.GetUIFileOrDirectoryName(), rev);
1545 wcname.Format(IDS_DIFF_WCNAME, (LPCTSTR)data->path.GetUIFileOrDirectoryName());
1546 CAppUtils::StartExtDiff(
1547 tempfile, data->path, revname, wcname,
1548 CAppUtils::DiffFlags().AlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000)));
1552 break;
1553 case ID_EDITCONFLICT:
1555 CString sPath = GetPathFromColumnText(data->sPathColumnText);
1556 SVNDiff::StartConflictEditor(CTGitPath(sPath));
1558 break;
1559 case ID_CONFLICTUSETHEIRS:
1560 case ID_CONFLICTUSEMINE:
1561 case ID_CONFLICTRESOLVE:
1563 svn_wc_conflict_choice_t result = svn_wc_conflict_choose_merged;
1564 switch (cmd)
1566 case ID_CONFLICTUSETHEIRS:
1567 result = svn_wc_conflict_choose_theirs_full;
1568 break;
1569 case ID_CONFLICTUSEMINE:
1570 result = svn_wc_conflict_choose_mine_full;
1571 break;
1572 case ID_CONFLICTRESOLVE:
1573 result = svn_wc_conflict_choose_merged;
1574 break;
1576 SVN svn;
1577 POSITION pos = GetFirstSelectedItemPosition();
1578 CString sResolvedPaths;
1579 while (pos)
1581 int nItem = GetNextSelectedItem(pos);
1582 NotificationData * data = m_arData[nItem];
1583 if (data)
1585 if (data->bConflictedActionItem)
1587 if (!svn.Resolve(data->path, result, FALSE))
1589 CMessageBox::Show(m_hWnd, svn.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1590 DialogEnableWindow(IDOK, TRUE);
1591 break;
1593 else
1595 data->color = ::GetSysColor(COLOR_WINDOWTEXT);
1596 data->action = svn_wc_notify_resolved;
1597 data->sActionColumnText.LoadString(IDS_SVNACTION_RESOLVE);
1598 data->bConflictedActionItem = false;
1599 m_nConflicts--;
1601 if (m_nConflicts==0)
1603 // When the last conflict is resolved we remove
1604 // the warning which we assume is in the last line.
1605 int nIndex = GetItemCount()-1;
1606 VERIFY(DeleteItem(nIndex));
1608 delete m_arData[nIndex];
1609 m_arData.pop_back();
1611 sResolvedPaths += data->path.GetWinPathString() + _T("\n");
1616 Invalidate();
1617 CString info = BuildInfoString();
1618 SetDlgItemText(IDC_INFOTEXT, info);
1620 if (!sResolvedPaths.IsEmpty())
1622 CString msg;
1623 msg.Format(IDS_SVNPROGRESS_RESOLVED, (LPCTSTR)sResolvedPaths);
1624 CMessageBox::Show(m_hWnd, msg, _T("TortoiseGit"), MB_OK | MB_ICONINFORMATION);
1627 break;
1628 #endif
1629 case ID_LOG:
1631 if (!data)
1632 return;
1633 CString cmd = _T("/command:log");
1634 CString sPath = GetPathFromColumnText(data->sPathColumnText);
1635 if(data->action == git_wc_notify_update_ref)
1637 cmd += _T(" /path:\"") + GetPathFromColumnText(CString()) + _T("\"");
1638 if (!data->m_OldHash.IsEmpty())
1639 cmd += _T(" /startrev:") + data->m_OldHash.ToString();
1640 if (!data->m_NewHash.IsEmpty())
1641 cmd += _T(" /endrev:") + data->m_NewHash.ToString();
1643 else
1644 cmd += _T(" /path:\"") + sPath + _T("\"");
1645 CAppUtils::RunTortoiseGitProc(cmd);
1647 break;
1648 case ID_OPENWITH:
1649 bOpenWith = true;
1650 case ID_OPEN:
1652 if (!data)
1653 return;
1654 int ret = 0;
1655 CString sWinPath = GetPathFromColumnText(data->sPathColumnText);
1656 if (!bOpenWith)
1657 ret = (int)ShellExecute(this->m_hWnd, NULL, (LPCTSTR)sWinPath, NULL, NULL, SW_SHOWNORMAL);
1658 if ((ret <= HINSTANCE_ERROR)||bOpenWith)
1660 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1661 cmd += sWinPath + _T(" ");
1662 CAppUtils::LaunchApplication(cmd, NULL, false);
1666 #if 0 //need
1667 DialogEnableWindow(IDOK, TRUE);
1668 #endif
1669 theApp.DoWaitCursor(-1);
1670 } // if (bAdded)
1676 void CGitProgressList::OnLvnBegindragSvnprogress(NMHDR* , LRESULT *pResult)
1678 //LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1679 #if 0
1680 int selIndex = GetSelectionMark();
1681 if (selIndex < 0)
1682 return;
1684 CDropFiles dropFiles; // class for creating DROPFILES struct
1686 int index;
1687 POSITION pos = GetFirstSelectedItemPosition();
1688 while ( (index = GetNextSelectedItem(pos)) >= 0 )
1690 NotificationData * data = m_arData[index];
1692 if ( data->kind==svn_node_file || data->kind==svn_node_dir )
1694 CString sPath = GetPathFromColumnText(data->sPathColumnText);
1696 dropFiles.AddFile( sPath );
1700 if (!dropFiles.IsEmpty())
1702 dropFiles.CreateStructure();
1704 #endif
1705 *pResult = 0;
1708 void CGitProgressList::OnSize(UINT nType, int cx, int cy)
1710 CListCtrl::OnSize(nType, cx, cy);
1711 if ((nType == SIZE_RESTORED)&&(m_bLastVisible))
1713 if(!m_hWnd)
1714 return;
1716 int count = GetItemCount();
1717 if (count > 0)
1718 EnsureVisible(count-1, false);
1722 //////////////////////////////////////////////////////////////////////////
1723 /// commands
1724 //////////////////////////////////////////////////////////////////////////
1725 bool CGitProgressList::CmdAdd(CString& sWindowTitle, bool& localoperation)
1727 localoperation = true;
1728 SetWindowTitle(IDS_PROGRS_TITLE_ADD, g_Git.CombinePath(m_targetPathList.GetCommonRoot().GetUIPathString()), sWindowTitle);
1729 SetBackgroundImage(IDI_ADD_BKG);
1730 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_ADD)));
1732 m_itemCountTotal = m_targetPathList.GetCount();
1734 if (g_Git.UsingLibGit2(CGit::GIT_CMD_ADD))
1736 CAutoRepository repo(g_Git.GetGitRepository());
1737 if (!repo)
1739 ReportGitError();
1740 return false;
1743 CAutoIndex index;
1744 if (git_repository_index(index.GetPointer(), repo))
1746 ReportGitError();
1747 return false;
1749 if (git_index_read(index, true))
1751 ReportGitError();
1752 return false;
1755 for (m_itemCount = 0; m_itemCount < m_itemCountTotal; ++m_itemCount)
1757 if (git_index_add_bypath(index, CUnicodeUtils::GetMulti(m_targetPathList[m_itemCount].GetGitPathString(), CP_UTF8)))
1759 ReportGitError();
1760 return false;
1762 Notify(m_targetPathList[m_itemCount], git_wc_notify_add);
1764 if (IsCancelled() == TRUE)
1766 ReportUserCanceled();
1767 return false;
1771 if (git_index_write(index))
1773 ReportGitError();
1774 return false;
1777 else
1779 CMassiveGitTask mgt(L"add -f");
1780 if (!mgt.ExecuteWithNotify(&m_targetPathList, m_bCancelled, git_wc_notify_add, this))
1781 return false;
1784 CShellUpdater::Instance().AddPathsForUpdate(m_targetPathList);
1786 return true;
1789 bool CGitProgressList::CmdCopy(CString& /*sWindowTitle*/, bool& /*localoperation*/)
1791 #if 0
1792 ASSERT(m_targetPathList.GetCount() == 1);
1793 sWindowTitle.LoadString(IDS_PROGRS_TITLE_COPY);
1794 SetWindowText(sWindowTitle); // needs to be updated, see TSVN rev. 21375
1795 SetBackgroundImage(IDI_COPY_BKG);
1797 CString sCmdInfo;
1798 sCmdInfo.Format(IDS_PROGRS_CMD_COPY,
1799 m_targetPathList[0].IsUrl() ? (LPCTSTR)m_targetPathList[0].GetSVNPathString() : m_targetPathList[0].GetWinPath(),
1800 (LPCTSTR)m_url.GetSVNPathString(), (LPCTSTR)m_Revision.ToString());
1801 ReportCmd(sCmdInfo);
1803 if (!Copy(m_targetPathList, m_url, m_Revision, m_pegRev, m_sMessage))
1805 ReportSVNError();
1806 return false;
1808 if (m_options & ProgOptSwitchAfterCopy)
1810 sCmdInfo.Format(IDS_PROGRS_CMD_SWITCH,
1811 m_targetPathList[0].GetWinPath(),
1812 (LPCTSTR)m_url.GetSVNPathString(), (LPCTSTR)m_Revision.ToString());
1813 ReportCmd(sCmdInfo);
1814 if (!Switch(m_targetPathList[0], m_url, SVNRev::REV_HEAD, SVNRev::REV_HEAD, m_depth, TRUE, m_options & ProgOptIgnoreExternals))
1816 if (!Switch(m_targetPathList[0], m_url, SVNRev::REV_HEAD, m_Revision, m_depth, TRUE, m_options & ProgOptIgnoreExternals))
1818 ReportSVNError();
1819 return false;
1823 else
1825 if (SVN::PathIsURL(m_url))
1827 CString sMsg(MAKEINTRESOURCE(IDS_PROGRS_COPY_WARNING));
1828 ReportNotification(sMsg);
1831 #endif
1832 return true;
1835 bool CGitProgressList::CmdExport(CString& /*sWindowTitle*/, bool& /*localoperation*/)
1837 #if 0
1838 ASSERT(m_targetPathList.GetCount() == 1);
1839 sWindowTitle.LoadString(IDS_PROGRS_TITLE_EXPORT);
1840 sWindowTitle = m_url.GetUIFileOrDirectoryName()+_T(" - ")+sWindowTitle;
1841 SetWindowText(sWindowTitle); // needs to be updated, see TSVN rev. 21375
1842 SetBackgroundImage(IDI_EXPORT_BKG);
1843 CString eol;
1844 if (m_options & ProgOptEolCRLF)
1845 eol = _T("CRLF");
1846 if (m_options & ProgOptEolLF)
1847 eol = _T("LF");
1848 if (m_options & ProgOptEolCR)
1849 eol = _T("CR");
1850 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_EXPORT)));
1851 if (!Export(m_url, m_targetPathList[0], m_Revision, m_Revision, TRUE, m_options & ProgOptIgnoreExternals, m_depth, NULL, FALSE, eol))
1853 ReportSVNError();
1854 return false;
1856 #endif
1857 return true;
1860 bool CGitProgressList::CmdRename(CString& /*sWindowTitle*/, bool& /*localoperation*/)
1862 #if 0
1863 ASSERT(m_targetPathList.GetCount() == 1);
1864 if ((!m_targetPathList[0].IsUrl())&&(!m_url.IsUrl()))
1865 localoperation = true;
1866 sWindowTitle.LoadString(IDS_PROGRS_TITLE_RENAME);
1867 SetWindowText(sWindowTitle); // needs to be updated, see TSVN rev. 21375
1868 SetBackgroundImage(IDI_RENAME_BKG);
1869 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_RENAME)));
1870 if (!Move(m_targetPathList, m_url, m_Revision, m_sMessage))
1872 ReportSVNError();
1873 return false;
1875 #endif
1876 return true;
1879 bool CGitProgressList::CmdResolve(CString& sWindowTitle, bool& localoperation)
1882 localoperation = true;
1883 ASSERT(m_targetPathList.GetCount() == 1);
1884 SetWindowTitle(IDS_PROGRS_TITLE_RESOLVE, g_Git.CombinePath(m_targetPathList.GetCommonRoot().GetUIPathString()), sWindowTitle);
1885 SetBackgroundImage(IDI_RESOLVE_BKG);
1886 // check if the file may still have conflict markers in it.
1887 //BOOL bMarkers = FALSE;
1889 m_itemCountTotal = m_targetPathList.GetCount();
1890 for (m_itemCount = 0; m_itemCount < m_itemCountTotal; ++m_itemCount)
1892 CString cmd,out,tempmergefile;
1893 cmd.Format(_T("git.exe add -f -- \"%s\""),m_targetPathList[m_itemCount].GetGitPathString());
1894 if (g_Git.Run(cmd, &out, CP_UTF8))
1896 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK|MB_ICONERROR);
1897 m_bErrorsOccurred=true;
1898 return false;
1901 CAppUtils::RemoveTempMergeFile((CTGitPath &)m_targetPathList[m_itemCount]);
1903 Notify(m_targetPathList[m_itemCount], git_wc_notify_resolved);
1905 #if 0
1906 if ((m_options & ProgOptSkipConflictCheck) == 0)
1910 for (INT_PTR fileindex=0; (fileindex<m_targetPathList.GetCount()) && (bMarkers==FALSE); ++fileindex)
1912 if (!m_targetPathList[fileindex].IsDirectory())
1914 CStdioFile file(m_targetPathList[fileindex].GetWinPath(), CFile::typeBinary | CFile::modeRead);
1915 CString strLine = _T("");
1916 while (file.ReadString(strLine))
1918 if (strLine.Find(_T("<<<<<<<"))==0)
1920 bMarkers = TRUE;
1921 break;
1924 file.Close();
1928 catch (CFileException* pE)
1930 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CFileException in Resolve!\n");
1931 TCHAR error[10000] = {0};
1932 pE->GetErrorMessage(error, 10000);
1933 ReportError(error);
1934 pE->Delete();
1935 return false;
1938 if (bMarkers)
1940 if (CMessageBox::Show(m_hWnd, IDS_PROGRS_REVERTMARKERS, IDS_APPNAME, MB_YESNO | MB_ICONQUESTION)==IDYES)
1942 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_RESOLVE)));
1943 for (INT_PTR fileindex=0; fileindex<m_targetPathList.GetCount(); ++fileindex)
1944 Resolve(m_targetPathList[fileindex], svn_wc_conflict_choose_merged, true);
1947 else
1949 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_RESOLVE)));
1950 for (INT_PTR fileindex=0; fileindex<m_targetPathList.GetCount(); ++fileindex)
1951 Resolve(m_targetPathList[fileindex], svn_wc_conflict_choose_merged, true);
1953 #endif
1954 CShellUpdater::Instance().AddPathsForUpdate(m_targetPathList);
1956 return true;
1959 bool CGitProgressList::CmdRevert(CString& sWindowTitle, bool& localoperation)
1962 localoperation = true;
1963 SetWindowTitle(IDS_PROGRS_TITLE_REVERT, g_Git.CombinePath(m_targetPathList.GetCommonRoot().GetUIPathString()), sWindowTitle);
1964 SetBackgroundImage(IDI_REVERT_BKG);
1966 m_itemCountTotal = 2 * m_selectedPaths.GetCount();
1967 CTGitPathList delList;
1968 for (m_itemCount = 0; m_itemCount < m_selectedPaths.GetCount(); ++m_itemCount)
1970 CTGitPath path;
1971 int action;
1972 path.SetFromWin(g_Git.CombinePath(m_selectedPaths[m_itemCount]));
1973 action = m_selectedPaths[m_itemCount].m_Action;
1974 /* rename file can't delete because it needs original file*/
1975 if((!(action & CTGitPath::LOGACTIONS_ADDED)) &&
1976 (!(action & CTGitPath::LOGACTIONS_REPLACED)))
1977 delList.AddPath(path);
1979 if (DWORD(CRegDWORD(_T("Software\\TortoiseGit\\RevertWithRecycleBin"), TRUE)))
1980 delList.DeleteAllFiles(true);
1982 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_REVERT)));
1983 for (int i = 0; i < m_selectedPaths.GetCount(); ++i)
1985 if(g_Git.Revert(_T("HEAD"), (CTGitPath&)m_selectedPaths[i]))
1987 CMessageBox::Show(NULL,_T("Revert Fail"),_T("TortoiseGit"),MB_OK|MB_ICONERROR);
1988 m_bErrorsOccurred=true;
1989 return false;
1991 Notify(m_selectedPaths[i], git_wc_notify_revert);
1992 ++m_itemCount;
1994 if (IsCancelled() == TRUE)
1996 ReportUserCanceled();
1997 return false;
2001 CShellUpdater::Instance().AddPathsForUpdate(m_selectedPaths);
2003 return true;
2006 bool CGitProgressList::CmdSwitch(CString& /*sWindowTitle*/, bool& /*localoperation*/)
2008 #if 0
2009 ASSERT(m_targetPathList.GetCount() == 1);
2010 SVNStatus st;
2011 sWindowTitle.LoadString(IDS_PROGRS_TITLE_SWITCH);
2012 SetWindowText(sWindowTitle); // needs to be updated, see TSVN rev. 21375
2013 SetBackgroundImage(IDI_SWITCH_BKG);
2014 LONG rev = 0;
2015 if (st.GetStatus(m_targetPathList[0]) != (-2))
2017 if (st.status->entry != NULL)
2019 rev = st.status->entry->revision;
2023 CString sCmdInfo;
2024 sCmdInfo.Format(IDS_PROGRS_CMD_SWITCH,
2025 m_targetPathList[0].GetWinPath(), (LPCTSTR)m_url.GetSVNPathString(),
2026 (LPCTSTR)m_Revision.ToString());
2027 ReportCmd(sCmdInfo);
2029 bool depthIsSticky = true;
2030 if (m_depth == svn_depth_unknown)
2031 depthIsSticky = false;
2032 if (!Switch(m_targetPathList[0], m_url, m_Revision, m_Revision, m_depth, depthIsSticky, m_options & ProgOptIgnoreExternals))
2034 ReportSVNError();
2035 return false;
2037 m_UpdateStartRevMap[m_targetPathList[0].GetSVNApiPath(pool)] = rev;
2038 if ((m_RevisionEnd >= 0)&&(rev >= 0)
2039 &&((LONG)m_RevisionEnd > (LONG)rev))
2041 GetDlgItem(IDC_LOGBUTTON)->ShowWindow(SW_SHOW);
2043 #endif
2044 return true;
2046 bool CGitProgressList::CmdClone(CString& sWindowTitle, bool& /*localoperation*/)
2048 if (!g_Git.UsingLibGit2(CGit::GIT_CMD_CLONE))
2050 // should never run to here
2051 ASSERT(FALSE);
2052 return false;
2054 this->m_TotalBytesTransferred = 0;
2056 SetWindowTitle(IDS_PROGRS_TITLE_CLONE, m_url.GetGitPathString(), sWindowTitle);
2057 SetBackgroundImage(IDI_SWITCH_BKG);
2058 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROG_CLONE)));
2060 if (m_url.IsEmpty() || m_targetPathList.IsEmpty())
2061 return false;
2063 git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
2064 checkout_opts.checkout_strategy = m_bNoCheckout? GIT_CHECKOUT_NONE : GIT_CHECKOUT_SAFE_CREATE;
2065 checkout_opts.progress_cb = CheckoutCallback;
2066 checkout_opts.progress_payload = this;
2068 git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
2069 callbacks.update_tips = RemoteUpdatetipsCallback;
2070 callbacks.sideband_progress = RemoteProgressCallback;
2071 callbacks.completion = RemoteCompletionCallback;
2072 callbacks.transfer_progress = FetchCallback;
2073 callbacks.credentials = CAppUtils::Git2GetUserPassword;
2074 callbacks.payload = this;
2076 CSmartAnimation animate(m_pAnimate);
2077 CAutoRepository cloned_repo;
2079 git_clone_options cloneOpts = GIT_CLONE_OPTIONS_INIT;
2080 cloneOpts.bare = m_bBare;
2081 cloneOpts.repository_cb = [](git_repository** out, const char* path, int bare, void* /*payload*/) -> int {
2082 git_repository_init_options init_options = GIT_REPOSITORY_INIT_OPTIONS_INIT;
2083 init_options.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE;
2084 init_options.flags |= bare ? GIT_REPOSITORY_INIT_BARE : 0;
2086 return git_repository_init_ext(out, path, &init_options);
2089 cloneOpts.remote_cb = [](git_remote** out, git_repository* repo, const char* /*name*/, const char* url, void* payload) -> int {
2090 int error;
2092 CAutoRemote origin;
2093 if ((error = git_remote_create(origin.GetPointer(), repo, (const char*)payload, url)) < 0)
2094 return error;
2096 *out = origin.Detach();
2097 return 0;
2099 cloneOpts.remote_callbacks = callbacks;
2100 CStringA remoteName = m_remote.IsEmpty() ? "origin" : CUnicodeUtils::GetUTF8(m_remote);
2101 cloneOpts.remote_cb_payload = (void*)(const char*)remoteName;
2103 CStringA checkout_branch = CUnicodeUtils::GetUTF8(m_RefSpec);
2104 if (!checkout_branch.IsEmpty())
2105 cloneOpts.checkout_branch = checkout_branch;
2106 cloneOpts.checkout_opts = checkout_opts;
2108 if (git_clone(cloned_repo.GetPointer(), CUnicodeUtils::GetUTF8(m_url.GetGitPathString()), CUnicodeUtils::GetUTF8(m_targetPathList[0].GetWinPathString()), &cloneOpts) < 0)
2109 goto error;
2111 return true;
2113 error:
2114 ReportGitError();
2115 return false;
2117 bool CGitProgressList::CmdSendMail(CString& sWindowTitle, bool& /*localoperation*/)
2119 ASSERT(m_SendMail);
2120 SetWindowTitle(IDS_PROGRS_TITLE_SENDMAIL, g_Git.CombinePath(m_targetPathList.GetCommonRoot().GetUIPathString()), sWindowTitle);
2121 //SetBackgroundImage(IDI_ADD_BKG);
2122 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_SENDMAIL)));
2124 return m_SendMail->Send(m_targetPathList, this) == 0;
2127 bool CGitProgressList::CmdFetch(CString& sWindowTitle, bool& /*localoperation*/)
2129 if (!g_Git.UsingLibGit2(CGit::GIT_CMD_FETCH))
2131 // should never run to here
2132 ASSERT(0);
2133 return false;
2135 this->m_TotalBytesTransferred = 0;
2137 SetWindowTitle(IDS_PROGRS_TITLE_FETCH, g_Git.m_CurrentDir, sWindowTitle);
2138 SetBackgroundImage(IDI_UPDATE_BKG);
2139 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_TITLE_FETCH)) + _T(" ") + m_url.GetGitPathString() + _T(" ") + m_RefSpec);
2141 CStringA url = CUnicodeUtils::GetMulti(m_url.GetGitPathString(), CP_UTF8);
2143 CSmartAnimation animate(m_pAnimate);
2145 CAutoRepository repo(g_Git.GetGitRepository());
2146 if (!repo)
2148 ReportGitError();
2149 return false;
2152 CAutoRemote remote;
2153 // first try with a named remote (e.g. "origin")
2154 if (git_remote_load(remote.GetPointer(), repo, url) < 0)
2156 // retry with repository located at a specific url
2157 if (git_remote_create_anonymous(remote.GetPointer(), repo, url, nullptr) < 0)
2159 ReportGitError();
2160 return false;
2164 git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
2165 callbacks.update_tips = RemoteUpdatetipsCallback;
2166 callbacks.sideband_progress = RemoteProgressCallback;
2167 callbacks.transfer_progress = FetchCallback;
2168 callbacks.completion = RemoteCompletionCallback;
2169 callbacks.credentials = CAppUtils::Git2GetUserPassword;
2170 callbacks.payload = this;
2172 git_remote_set_callbacks(remote, &callbacks);
2173 git_remote_set_autotag(remote, (git_remote_autotag_option_t)m_AutoTag);
2175 if (!m_RefSpec.IsEmpty() && git_remote_add_fetch(remote, CUnicodeUtils::GetUTF8(m_RefSpec)))
2176 goto error;
2178 // Connect to the remote end specifying that we want to fetch
2179 // information from it.
2180 if (git_remote_connect(remote, GIT_DIRECTION_FETCH) < 0)
2181 goto error;
2183 // Download the packfile and index it. This function updates the
2184 // amount of received data and the indexer stats which lets you
2185 // inform the user about progress.
2186 if (git_remote_download(remote) < 0)
2187 goto error;
2189 // Update the references in the remote's namespace to point to the
2190 // right commits. This may be needed even if there was no packfile
2191 // to download, which can happen e.g. when the branches have been
2192 // changed but all the neede objects are available locally.
2193 if (git_remote_update_tips(remote, nullptr, nullptr) < 0)
2194 goto error;
2196 git_remote_disconnect(remote);
2198 return true;
2200 error:
2201 ReportGitError();
2202 return false;
2205 bool CGitProgressList::CmdReset(CString& sWindowTitle, bool& /*localoperation*/)
2207 if (!g_Git.UsingLibGit2(CGit::GIT_CMD_RESET))
2209 // should never run to here
2210 ASSERT(0);
2211 return false;
2214 SetWindowTitle(IDS_PROGRS_TITLE_RESET, g_Git.m_CurrentDir, sWindowTitle);
2215 SetBackgroundImage(IDI_UPDATE_BKG);
2216 int resetTypesResource[] = { IDS_RESET_SOFT, IDS_RESET_MIXED, IDS_RESET_HARD };
2217 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_TITLE_RESET)) + _T(" ") + CString(MAKEINTRESOURCE(resetTypesResource[m_resetType])) + _T(" ") + m_revision);
2219 CAutoRepository repo(g_Git.GetGitRepository());
2220 if (!repo)
2222 ReportGitError();
2223 return false;
2226 CSmartAnimation animate(m_pAnimate);
2227 CAutoObject target;
2228 if (git_revparse_single(target.GetPointer(), repo, CUnicodeUtils::GetUTF8(m_revision)))
2229 goto error;
2230 if (git_reset(repo, target, (git_reset_t)(m_resetType + 1), nullptr, nullptr))
2231 goto error;
2233 return true;
2235 error:
2236 ReportGitError();
2237 return false;
2241 void CGitProgressList::Init()
2243 SetExtendedStyle (LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER);
2245 DeleteAllItems();
2246 int c = ((CHeaderCtrl*)(GetDlgItem(0)))->GetItemCount()-1;
2247 while (c>=0)
2248 DeleteColumn(c--);
2250 CString temp;
2251 temp.LoadString(IDS_PROGRS_ACTION);
2252 InsertColumn(0, temp);
2253 temp.LoadString(IDS_PROGRS_PATH);
2254 InsertColumn(1, temp);
2256 m_pThread = AfxBeginThread(ProgressThreadEntry, this, THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED);
2257 if (m_pThread==NULL)
2259 ReportError(CString(MAKEINTRESOURCE(IDS_ERR_THREADSTARTFAILED)));
2261 else
2263 m_pThread->m_bAutoDelete = FALSE;
2264 m_pThread->ResumeThread();
2267 // Call this early so that the column headings aren't hidden before any
2268 // text gets added.
2269 ResizeColumns();
2271 SetTimer(VISIBLETIMER, 300, NULL);
2275 void CGitProgressList::OnClose()
2277 // TODO: Add your message handler code here and/or call default
2278 if (m_bCancelled)
2280 TerminateThread(m_pThread->m_hThread, (DWORD)-1);
2281 InterlockedExchange(&m_bThreadRunning, FALSE);
2283 else
2285 m_bCancelled = TRUE;
2286 return;
2288 CListCtrl::OnClose();
2292 BOOL CGitProgressList::PreTranslateMessage(MSG* pMsg)
2294 // TODO: Add your specialized code here and/or call the base class
2295 if (pMsg->message == WM_KEYDOWN)
2297 if (pMsg->wParam == 'A')
2299 if (GetKeyState(VK_CONTROL)&0x8000)
2301 // Ctrl-A -> select all
2302 SetSelectionMark(0);
2303 for (int i=0; i<GetItemCount(); ++i)
2305 SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
2309 if ((pMsg->wParam == 'C')||(pMsg->wParam == VK_INSERT))
2311 int selIndex = GetSelectionMark();
2312 if (selIndex >= 0)
2314 if (GetKeyState(VK_CONTROL)&0x8000)
2316 //Ctrl-C -> copy to clipboard
2317 CString sClipdata;
2318 POSITION pos = GetFirstSelectedItemPosition();
2319 if (pos != NULL)
2321 while (pos)
2323 int nItem = GetNextSelectedItem(pos);
2324 CString sAction = GetItemText(nItem, 0);
2325 CString sPath = GetItemText(nItem, 1);
2326 CString sMime = GetItemText(nItem, 2);
2327 CString sLogCopyText;
2328 sLogCopyText.Format(_T("%s: %s %s\r\n"),
2329 (LPCTSTR)sAction, (LPCTSTR)sPath, (LPCTSTR)sMime);
2330 sClipdata += sLogCopyText;
2332 CStringUtils::WriteAsciiStringToClipboard(sClipdata);
2337 } // if (pMsg->message == WM_KEYDOWN)
2338 return CListCtrl::PreTranslateMessage(pMsg);
2341 CString CGitProgressList::GetPathFromColumnText(const CString& sColumnText)
2343 CString sPath = sColumnText;
2344 if (sPath.Find(':')<0)
2346 // the path is not absolute: add the common root of all paths to it
2347 sPath = g_Git.CombinePath(sColumnText);
2349 return sPath;
2352 void CGitProgressList::SetWindowTitle(UINT id, const CString& urlorpath, CString& dialogname)
2354 if (!m_bSetTitle || !m_pPostWnd)
2355 return;
2357 dialogname.LoadString(id);
2358 CAppUtils::SetWindowTitle(m_pPostWnd->GetSafeHwnd(), urlorpath, dialogname);