Add start and pre commit hooks
[TortoiseGit.git] / src / TortoiseProc / GitProgressList.cpp
blob1dfdd34f26dc44fe047e0e7ac1efd4ad8f09c712
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)
1462 CString sPath = GetPathFromColumnText(data->sPathColumnText);
1464 CTGitPath path = CTGitPath(sPath);
1465 ShellExecute(m_hWnd, _T("explore"), path.GetDirectory().GetWinPath(), NULL, path.GetDirectory().GetWinPath(), SW_SHOW);
1468 break;
1469 #if 0
1470 case ID_COMPARE:
1472 svn_revnum_t rev = -1;
1473 StringRevMap::iterator it = m_UpdateStartRevMap.end();
1474 if (data->basepath.IsEmpty())
1475 it = m_UpdateStartRevMap.begin();
1476 else
1477 it = m_UpdateStartRevMap.find(data->basepath.GetSVNApiPath(pool));
1478 if (it != m_UpdateStartRevMap.end())
1479 rev = it->second;
1480 // if the file was merged during update, do a three way diff between OLD, MINE, THEIRS
1481 if (data->content_state == svn_wc_notify_state_merged)
1483 CTGitPath basefile = CTempFiles::Instance().GetTempFilePath(false, data->path, rev);
1484 CTGitPath newfile = CTempFiles::Instance().GetTempFilePath(false, data->path, SVNRev::REV_HEAD);
1485 SVN svn;
1486 if (!svn.Cat(data->path, SVNRev(SVNRev::REV_WC), rev, basefile))
1488 CMessageBox::Show(m_hWnd, svn.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1489 DialogEnableWindow(IDOK, TRUE);
1490 break;
1492 // If necessary, convert the line-endings on the file before diffing
1493 if ((DWORD)CRegDWORD(_T("Software\\TortoiseGit\\ConvertBase"), TRUE))
1495 CTGitPath temporaryFile = CTempFiles::Instance().GetTempFilePath(false, data->path, SVNRev::REV_BASE);
1496 if (!svn.Cat(data->path, SVNRev(SVNRev::REV_BASE), SVNRev(SVNRev::REV_BASE), temporaryFile))
1498 temporaryFile.Reset();
1499 break;
1501 else
1503 newfile = temporaryFile;
1507 SetFileAttributes(newfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1508 SetFileAttributes(basefile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1509 CString revname, wcname, basename;
1510 revname.Format(_T("%s Revision %ld"), (LPCTSTR)data->path.GetUIFileOrDirectoryName(), rev);
1511 wcname.Format(IDS_DIFF_WCNAME, (LPCTSTR)data->path.GetUIFileOrDirectoryName());
1512 basename.Format(IDS_DIFF_BASENAME, (LPCTSTR)data->path.GetUIFileOrDirectoryName());
1513 CAppUtils::StartExtMerge(basefile, newfile, data->path, data->path, basename, revname, wcname, CString(), true);
1515 else
1517 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, data->path, rev);
1518 SVN svn;
1519 if (!svn.Cat(data->path, SVNRev(SVNRev::REV_WC), rev, tempfile))
1521 CMessageBox::Show(m_hWnd, svn.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1522 DialogEnableWindow(IDOK, TRUE);
1523 break;
1525 else
1527 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1528 CString revname, wcname;
1529 revname.Format(_T("%s Revision %ld"), (LPCTSTR)data->path.GetUIFileOrDirectoryName(), rev);
1530 wcname.Format(IDS_DIFF_WCNAME, (LPCTSTR)data->path.GetUIFileOrDirectoryName());
1531 CAppUtils::StartExtDiff(
1532 tempfile, data->path, revname, wcname,
1533 CAppUtils::DiffFlags().AlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000)));
1537 break;
1538 case ID_EDITCONFLICT:
1540 CString sPath = GetPathFromColumnText(data->sPathColumnText);
1541 SVNDiff::StartConflictEditor(CTGitPath(sPath));
1543 break;
1544 case ID_CONFLICTUSETHEIRS:
1545 case ID_CONFLICTUSEMINE:
1546 case ID_CONFLICTRESOLVE:
1548 svn_wc_conflict_choice_t result = svn_wc_conflict_choose_merged;
1549 switch (cmd)
1551 case ID_CONFLICTUSETHEIRS:
1552 result = svn_wc_conflict_choose_theirs_full;
1553 break;
1554 case ID_CONFLICTUSEMINE:
1555 result = svn_wc_conflict_choose_mine_full;
1556 break;
1557 case ID_CONFLICTRESOLVE:
1558 result = svn_wc_conflict_choose_merged;
1559 break;
1561 SVN svn;
1562 POSITION pos = GetFirstSelectedItemPosition();
1563 CString sResolvedPaths;
1564 while (pos)
1566 int nItem = GetNextSelectedItem(pos);
1567 NotificationData * data = m_arData[nItem];
1568 if (data)
1570 if (data->bConflictedActionItem)
1572 if (!svn.Resolve(data->path, result, FALSE))
1574 CMessageBox::Show(m_hWnd, svn.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1575 DialogEnableWindow(IDOK, TRUE);
1576 break;
1578 else
1580 data->color = ::GetSysColor(COLOR_WINDOWTEXT);
1581 data->action = svn_wc_notify_resolved;
1582 data->sActionColumnText.LoadString(IDS_SVNACTION_RESOLVE);
1583 data->bConflictedActionItem = false;
1584 m_nConflicts--;
1586 if (m_nConflicts==0)
1588 // When the last conflict is resolved we remove
1589 // the warning which we assume is in the last line.
1590 int nIndex = GetItemCount()-1;
1591 VERIFY(DeleteItem(nIndex));
1593 delete m_arData[nIndex];
1594 m_arData.pop_back();
1596 sResolvedPaths += data->path.GetWinPathString() + _T("\n");
1601 Invalidate();
1602 CString info = BuildInfoString();
1603 SetDlgItemText(IDC_INFOTEXT, info);
1605 if (!sResolvedPaths.IsEmpty())
1607 CString msg;
1608 msg.Format(IDS_SVNPROGRESS_RESOLVED, (LPCTSTR)sResolvedPaths);
1609 CMessageBox::Show(m_hWnd, msg, _T("TortoiseGit"), MB_OK | MB_ICONINFORMATION);
1612 break;
1613 #endif
1614 case ID_LOG:
1616 if (!data)
1617 return;
1618 CString cmd = _T("/command:log");
1619 CString sPath = GetPathFromColumnText(data->sPathColumnText);
1620 if(data->action == git_wc_notify_update_ref)
1622 cmd += _T(" /path:\"") + GetPathFromColumnText(CString()) + _T("\"");
1623 if (!data->m_OldHash.IsEmpty())
1624 cmd += _T(" /startrev:") + data->m_OldHash.ToString();
1625 if (!data->m_NewHash.IsEmpty())
1626 cmd += _T(" /endrev:") + data->m_NewHash.ToString();
1628 else
1629 cmd += _T(" /path:\"") + sPath + _T("\"");
1630 CAppUtils::RunTortoiseGitProc(cmd);
1632 break;
1633 case ID_OPENWITH:
1634 bOpenWith = true;
1635 case ID_OPEN:
1637 if (!data)
1638 return;
1639 int ret = 0;
1640 CString sWinPath = GetPathFromColumnText(data->sPathColumnText);
1641 if (!bOpenWith)
1642 ret = (int)ShellExecute(this->m_hWnd, NULL, (LPCTSTR)sWinPath, NULL, NULL, SW_SHOWNORMAL);
1643 if ((ret <= HINSTANCE_ERROR)||bOpenWith)
1645 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1646 cmd += sWinPath + _T(" ");
1647 CAppUtils::LaunchApplication(cmd, NULL, false);
1651 #if 0 //need
1652 DialogEnableWindow(IDOK, TRUE);
1653 #endif
1654 theApp.DoWaitCursor(-1);
1655 } // if (bAdded)
1661 void CGitProgressList::OnLvnBegindragSvnprogress(NMHDR* , LRESULT *pResult)
1663 //LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1664 #if 0
1665 int selIndex = GetSelectionMark();
1666 if (selIndex < 0)
1667 return;
1669 CDropFiles dropFiles; // class for creating DROPFILES struct
1671 int index;
1672 POSITION pos = GetFirstSelectedItemPosition();
1673 while ( (index = GetNextSelectedItem(pos)) >= 0 )
1675 NotificationData * data = m_arData[index];
1677 if ( data->kind==svn_node_file || data->kind==svn_node_dir )
1679 CString sPath = GetPathFromColumnText(data->sPathColumnText);
1681 dropFiles.AddFile( sPath );
1685 if (!dropFiles.IsEmpty())
1687 dropFiles.CreateStructure();
1689 #endif
1690 *pResult = 0;
1693 void CGitProgressList::OnSize(UINT nType, int cx, int cy)
1695 CListCtrl::OnSize(nType, cx, cy);
1696 if ((nType == SIZE_RESTORED)&&(m_bLastVisible))
1698 if(!m_hWnd)
1699 return;
1701 int count = GetItemCount();
1702 if (count > 0)
1703 EnsureVisible(count-1, false);
1707 //////////////////////////////////////////////////////////////////////////
1708 /// commands
1709 //////////////////////////////////////////////////////////////////////////
1710 bool CGitProgressList::CmdAdd(CString& sWindowTitle, bool& localoperation)
1712 localoperation = true;
1713 SetWindowTitle(IDS_PROGRS_TITLE_ADD, g_Git.CombinePath(m_targetPathList.GetCommonRoot().GetUIPathString()), sWindowTitle);
1714 SetBackgroundImage(IDI_ADD_BKG);
1715 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_ADD)));
1717 m_itemCountTotal = m_targetPathList.GetCount();
1719 if (CRegDWORD(_T("Software\\TortoiseGit\\UseLibgit2"), TRUE) == TRUE)
1721 CAutoRepository repo(g_Git.GetGitRepository());
1722 if (!repo)
1724 ReportGitError();
1725 return false;
1728 CAutoIndex index;
1729 if (git_repository_index(index.GetPointer(), repo))
1731 ReportGitError();
1732 return false;
1734 if (git_index_read(index, true))
1736 ReportGitError();
1737 return false;
1740 for (m_itemCount = 0; m_itemCount < m_itemCountTotal; ++m_itemCount)
1742 if (git_index_add_bypath(index, CUnicodeUtils::GetMulti(m_targetPathList[m_itemCount].GetGitPathString(), CP_UTF8)))
1744 ReportGitError();
1745 return false;
1747 Notify(m_targetPathList[m_itemCount], git_wc_notify_add);
1749 if (IsCancelled() == TRUE)
1751 ReportUserCanceled();
1752 return false;
1756 if (git_index_write(index))
1758 ReportGitError();
1759 return false;
1762 else
1764 CMassiveGitTask mgt(L"add -f");
1765 if (!mgt.ExecuteWithNotify(&m_targetPathList, m_bCancelled, git_wc_notify_add, this))
1766 return false;
1769 CShellUpdater::Instance().AddPathsForUpdate(m_targetPathList);
1771 return true;
1774 bool CGitProgressList::CmdCopy(CString& /*sWindowTitle*/, bool& /*localoperation*/)
1776 #if 0
1777 ASSERT(m_targetPathList.GetCount() == 1);
1778 sWindowTitle.LoadString(IDS_PROGRS_TITLE_COPY);
1779 SetWindowText(sWindowTitle); // needs to be updated, see TSVN rev. 21375
1780 SetBackgroundImage(IDI_COPY_BKG);
1782 CString sCmdInfo;
1783 sCmdInfo.Format(IDS_PROGRS_CMD_COPY,
1784 m_targetPathList[0].IsUrl() ? (LPCTSTR)m_targetPathList[0].GetSVNPathString() : m_targetPathList[0].GetWinPath(),
1785 (LPCTSTR)m_url.GetSVNPathString(), (LPCTSTR)m_Revision.ToString());
1786 ReportCmd(sCmdInfo);
1788 if (!Copy(m_targetPathList, m_url, m_Revision, m_pegRev, m_sMessage))
1790 ReportSVNError();
1791 return false;
1793 if (m_options & ProgOptSwitchAfterCopy)
1795 sCmdInfo.Format(IDS_PROGRS_CMD_SWITCH,
1796 m_targetPathList[0].GetWinPath(),
1797 (LPCTSTR)m_url.GetSVNPathString(), (LPCTSTR)m_Revision.ToString());
1798 ReportCmd(sCmdInfo);
1799 if (!Switch(m_targetPathList[0], m_url, SVNRev::REV_HEAD, SVNRev::REV_HEAD, m_depth, TRUE, m_options & ProgOptIgnoreExternals))
1801 if (!Switch(m_targetPathList[0], m_url, SVNRev::REV_HEAD, m_Revision, m_depth, TRUE, m_options & ProgOptIgnoreExternals))
1803 ReportSVNError();
1804 return false;
1808 else
1810 if (SVN::PathIsURL(m_url))
1812 CString sMsg(MAKEINTRESOURCE(IDS_PROGRS_COPY_WARNING));
1813 ReportNotification(sMsg);
1816 #endif
1817 return true;
1820 bool CGitProgressList::CmdExport(CString& /*sWindowTitle*/, bool& /*localoperation*/)
1822 #if 0
1823 ASSERT(m_targetPathList.GetCount() == 1);
1824 sWindowTitle.LoadString(IDS_PROGRS_TITLE_EXPORT);
1825 sWindowTitle = m_url.GetUIFileOrDirectoryName()+_T(" - ")+sWindowTitle;
1826 SetWindowText(sWindowTitle); // needs to be updated, see TSVN rev. 21375
1827 SetBackgroundImage(IDI_EXPORT_BKG);
1828 CString eol;
1829 if (m_options & ProgOptEolCRLF)
1830 eol = _T("CRLF");
1831 if (m_options & ProgOptEolLF)
1832 eol = _T("LF");
1833 if (m_options & ProgOptEolCR)
1834 eol = _T("CR");
1835 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_EXPORT)));
1836 if (!Export(m_url, m_targetPathList[0], m_Revision, m_Revision, TRUE, m_options & ProgOptIgnoreExternals, m_depth, NULL, FALSE, eol))
1838 ReportSVNError();
1839 return false;
1841 #endif
1842 return true;
1845 bool CGitProgressList::CmdRename(CString& /*sWindowTitle*/, bool& /*localoperation*/)
1847 #if 0
1848 ASSERT(m_targetPathList.GetCount() == 1);
1849 if ((!m_targetPathList[0].IsUrl())&&(!m_url.IsUrl()))
1850 localoperation = true;
1851 sWindowTitle.LoadString(IDS_PROGRS_TITLE_RENAME);
1852 SetWindowText(sWindowTitle); // needs to be updated, see TSVN rev. 21375
1853 SetBackgroundImage(IDI_RENAME_BKG);
1854 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_RENAME)));
1855 if (!Move(m_targetPathList, m_url, m_Revision, m_sMessage))
1857 ReportSVNError();
1858 return false;
1860 #endif
1861 return true;
1864 bool CGitProgressList::CmdResolve(CString& sWindowTitle, bool& localoperation)
1867 localoperation = true;
1868 ASSERT(m_targetPathList.GetCount() == 1);
1869 SetWindowTitle(IDS_PROGRS_TITLE_RESOLVE, g_Git.CombinePath(m_targetPathList.GetCommonRoot().GetUIPathString()), sWindowTitle);
1870 SetBackgroundImage(IDI_RESOLVE_BKG);
1871 // check if the file may still have conflict markers in it.
1872 //BOOL bMarkers = FALSE;
1874 m_itemCountTotal = m_targetPathList.GetCount();
1875 for (m_itemCount = 0; m_itemCount < m_itemCountTotal; ++m_itemCount)
1877 CString cmd,out,tempmergefile;
1878 cmd.Format(_T("git.exe add -f -- \"%s\""),m_targetPathList[m_itemCount].GetGitPathString());
1879 if (g_Git.Run(cmd, &out, CP_UTF8))
1881 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK|MB_ICONERROR);
1882 m_bErrorsOccurred=true;
1883 return false;
1886 CAppUtils::RemoveTempMergeFile((CTGitPath &)m_targetPathList[m_itemCount]);
1888 Notify(m_targetPathList[m_itemCount], git_wc_notify_resolved);
1890 #if 0
1891 if ((m_options & ProgOptSkipConflictCheck) == 0)
1895 for (INT_PTR fileindex=0; (fileindex<m_targetPathList.GetCount()) && (bMarkers==FALSE); ++fileindex)
1897 if (!m_targetPathList[fileindex].IsDirectory())
1899 CStdioFile file(m_targetPathList[fileindex].GetWinPath(), CFile::typeBinary | CFile::modeRead);
1900 CString strLine = _T("");
1901 while (file.ReadString(strLine))
1903 if (strLine.Find(_T("<<<<<<<"))==0)
1905 bMarkers = TRUE;
1906 break;
1909 file.Close();
1913 catch (CFileException* pE)
1915 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CFileException in Resolve!\n");
1916 TCHAR error[10000] = {0};
1917 pE->GetErrorMessage(error, 10000);
1918 ReportError(error);
1919 pE->Delete();
1920 return false;
1923 if (bMarkers)
1925 if (CMessageBox::Show(m_hWnd, IDS_PROGRS_REVERTMARKERS, IDS_APPNAME, MB_YESNO | MB_ICONQUESTION)==IDYES)
1927 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_RESOLVE)));
1928 for (INT_PTR fileindex=0; fileindex<m_targetPathList.GetCount(); ++fileindex)
1929 Resolve(m_targetPathList[fileindex], svn_wc_conflict_choose_merged, true);
1932 else
1934 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_RESOLVE)));
1935 for (INT_PTR fileindex=0; fileindex<m_targetPathList.GetCount(); ++fileindex)
1936 Resolve(m_targetPathList[fileindex], svn_wc_conflict_choose_merged, true);
1938 #endif
1939 CShellUpdater::Instance().AddPathsForUpdate(m_targetPathList);
1941 return true;
1944 bool CGitProgressList::CmdRevert(CString& sWindowTitle, bool& localoperation)
1947 localoperation = true;
1948 SetWindowTitle(IDS_PROGRS_TITLE_REVERT, g_Git.CombinePath(m_targetPathList.GetCommonRoot().GetUIPathString()), sWindowTitle);
1949 SetBackgroundImage(IDI_REVERT_BKG);
1951 m_itemCountTotal = 2 * m_selectedPaths.GetCount();
1952 CTGitPathList delList;
1953 for (m_itemCount = 0; m_itemCount < m_selectedPaths.GetCount(); ++m_itemCount)
1955 CTGitPath path;
1956 int action;
1957 path.SetFromWin(g_Git.CombinePath(m_selectedPaths[m_itemCount]));
1958 action = m_selectedPaths[m_itemCount].m_Action;
1959 /* rename file can't delete because it needs original file*/
1960 if((!(action & CTGitPath::LOGACTIONS_ADDED)) &&
1961 (!(action & CTGitPath::LOGACTIONS_REPLACED)))
1962 delList.AddPath(path);
1964 if (DWORD(CRegDWORD(_T("Software\\TortoiseGit\\RevertWithRecycleBin"), TRUE)))
1965 delList.DeleteAllFiles(true);
1967 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_REVERT)));
1968 for (int i = 0; i < m_selectedPaths.GetCount(); ++i)
1970 if(g_Git.Revert(_T("HEAD"), (CTGitPath&)m_selectedPaths[i]))
1972 CMessageBox::Show(NULL,_T("Revert Fail"),_T("TortoiseGit"),MB_OK|MB_ICONERROR);
1973 m_bErrorsOccurred=true;
1974 return false;
1976 Notify(m_selectedPaths[i], git_wc_notify_revert);
1977 ++m_itemCount;
1979 if (IsCancelled() == TRUE)
1981 ReportUserCanceled();
1982 return false;
1986 CShellUpdater::Instance().AddPathsForUpdate(m_selectedPaths);
1988 return true;
1991 bool CGitProgressList::CmdSwitch(CString& /*sWindowTitle*/, bool& /*localoperation*/)
1993 #if 0
1994 ASSERT(m_targetPathList.GetCount() == 1);
1995 SVNStatus st;
1996 sWindowTitle.LoadString(IDS_PROGRS_TITLE_SWITCH);
1997 SetWindowText(sWindowTitle); // needs to be updated, see TSVN rev. 21375
1998 SetBackgroundImage(IDI_SWITCH_BKG);
1999 LONG rev = 0;
2000 if (st.GetStatus(m_targetPathList[0]) != (-2))
2002 if (st.status->entry != NULL)
2004 rev = st.status->entry->revision;
2008 CString sCmdInfo;
2009 sCmdInfo.Format(IDS_PROGRS_CMD_SWITCH,
2010 m_targetPathList[0].GetWinPath(), (LPCTSTR)m_url.GetSVNPathString(),
2011 (LPCTSTR)m_Revision.ToString());
2012 ReportCmd(sCmdInfo);
2014 bool depthIsSticky = true;
2015 if (m_depth == svn_depth_unknown)
2016 depthIsSticky = false;
2017 if (!Switch(m_targetPathList[0], m_url, m_Revision, m_Revision, m_depth, depthIsSticky, m_options & ProgOptIgnoreExternals))
2019 ReportSVNError();
2020 return false;
2022 m_UpdateStartRevMap[m_targetPathList[0].GetSVNApiPath(pool)] = rev;
2023 if ((m_RevisionEnd >= 0)&&(rev >= 0)
2024 &&((LONG)m_RevisionEnd > (LONG)rev))
2026 GetDlgItem(IDC_LOGBUTTON)->ShowWindow(SW_SHOW);
2028 #endif
2029 return true;
2031 bool CGitProgressList::CmdClone(CString& sWindowTitle, bool& /*localoperation*/)
2033 if (!g_Git.UsingLibGit2(CGit::GIT_CMD_CLONE))
2035 // should never run to here
2036 ASSERT(FALSE);
2037 return false;
2039 this->m_TotalBytesTransferred = 0;
2041 SetWindowTitle(IDS_PROGRS_TITLE_CLONE, m_url.GetGitPathString(), sWindowTitle);
2042 SetBackgroundImage(IDI_SWITCH_BKG);
2043 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROG_CLONE)));
2045 if (m_url.IsEmpty() || m_targetPathList.IsEmpty())
2046 return false;
2048 git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
2049 checkout_opts.checkout_strategy = m_bNoCheckout? GIT_CHECKOUT_NONE : GIT_CHECKOUT_SAFE_CREATE;
2050 checkout_opts.progress_cb = CheckoutCallback;
2051 checkout_opts.progress_payload = this;
2053 git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
2054 callbacks.update_tips = RemoteUpdatetipsCallback;
2055 callbacks.sideband_progress = RemoteProgressCallback;
2056 callbacks.completion = RemoteCompletionCallback;
2057 callbacks.transfer_progress = FetchCallback;
2058 callbacks.credentials = CAppUtils::Git2GetUserPassword;
2059 callbacks.payload = this;
2061 git_repository_init_options init_options = GIT_REPOSITORY_INIT_OPTIONS_INIT;
2062 init_options.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE;
2063 init_options.flags |= m_bBare ? GIT_REPOSITORY_INIT_BARE : 0;
2065 CSmartAnimation animate(m_pAnimate);
2066 CAutoRepository cloned_repo;
2067 CAutoRemote origin;
2068 if (git_repository_init_ext(cloned_repo.GetPointer(), CUnicodeUtils::GetUTF8(m_targetPathList[0].GetWinPathString()), &init_options) < 0)
2069 goto error;
2070 if (git_remote_create(origin.GetPointer(), cloned_repo, m_remote.IsEmpty() ? "origin" : CUnicodeUtils::GetUTF8(m_remote), CUnicodeUtils::GetUTF8(m_url.GetGitPathString())) < 0)
2071 goto error;
2072 git_remote_set_callbacks(origin, &callbacks);
2073 if (git_clone_into(cloned_repo, origin, &checkout_opts, m_RefSpec.IsEmpty() ? nullptr : (const char *)CUnicodeUtils::GetUTF8(m_RefSpec), nullptr) < 0)
2074 goto error;
2076 return true;
2078 error:
2079 ReportGitError();
2080 return false;
2082 bool CGitProgressList::CmdSendMail(CString& sWindowTitle, bool& /*localoperation*/)
2084 ASSERT(m_SendMail);
2085 SetWindowTitle(IDS_PROGRS_TITLE_SENDMAIL, g_Git.CombinePath(m_targetPathList.GetCommonRoot().GetUIPathString()), sWindowTitle);
2086 //SetBackgroundImage(IDI_ADD_BKG);
2087 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_SENDMAIL)));
2089 return m_SendMail->Send(m_targetPathList, this) == 0;
2092 bool CGitProgressList::CmdFetch(CString& sWindowTitle, bool& /*localoperation*/)
2094 if (!g_Git.UsingLibGit2(CGit::GIT_CMD_FETCH))
2096 // should never run to here
2097 ASSERT(0);
2098 return false;
2100 this->m_TotalBytesTransferred = 0;
2102 SetWindowTitle(IDS_PROGRS_TITLE_FETCH, g_Git.m_CurrentDir, sWindowTitle);
2103 SetBackgroundImage(IDI_UPDATE_BKG);
2104 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_TITLE_FETCH)) + _T(" ") + m_url.GetGitPathString() + _T(" ") + m_RefSpec);
2106 CStringA url = CUnicodeUtils::GetMulti(m_url.GetGitPathString(), CP_UTF8);
2108 CSmartAnimation animate(m_pAnimate);
2110 CAutoRepository repo(g_Git.GetGitRepository());
2111 if (!repo)
2113 ReportGitError();
2114 return false;
2117 CAutoRemote remote;
2118 // first try with a named remote (e.g. "origin")
2119 if (git_remote_load(remote.GetPointer(), repo, url) < 0)
2121 // retry with repository located at a specific url
2122 if (git_remote_create_anonymous(remote.GetPointer(), repo, url, nullptr) < 0)
2124 ReportGitError();
2125 return false;
2129 git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
2130 callbacks.update_tips = RemoteUpdatetipsCallback;
2131 callbacks.sideband_progress = RemoteProgressCallback;
2132 callbacks.transfer_progress = FetchCallback;
2133 callbacks.completion = RemoteCompletionCallback;
2134 callbacks.credentials = CAppUtils::Git2GetUserPassword;
2135 callbacks.payload = this;
2137 git_remote_set_callbacks(remote, &callbacks);
2138 git_remote_set_autotag(remote, (git_remote_autotag_option_t)m_AutoTag);
2140 if (!m_RefSpec.IsEmpty() && git_remote_add_fetch(remote, CUnicodeUtils::GetUTF8(m_RefSpec)))
2141 goto error;
2143 // Connect to the remote end specifying that we want to fetch
2144 // information from it.
2145 if (git_remote_connect(remote, GIT_DIRECTION_FETCH) < 0)
2146 goto error;
2148 // Download the packfile and index it. This function updates the
2149 // amount of received data and the indexer stats which lets you
2150 // inform the user about progress.
2151 if (git_remote_download(remote) < 0)
2152 goto error;
2154 // Update the references in the remote's namespace to point to the
2155 // right commits. This may be needed even if there was no packfile
2156 // to download, which can happen e.g. when the branches have been
2157 // changed but all the neede objects are available locally.
2158 if (git_remote_update_tips(remote, nullptr, nullptr) < 0)
2159 goto error;
2161 git_remote_disconnect(remote);
2163 return true;
2165 error:
2166 ReportGitError();
2167 return false;
2170 bool CGitProgressList::CmdReset(CString& sWindowTitle, bool& /*localoperation*/)
2172 if (!g_Git.UsingLibGit2(CGit::GIT_CMD_RESET))
2174 // should never run to here
2175 ASSERT(0);
2176 return false;
2179 SetWindowTitle(IDS_PROGRS_TITLE_RESET, g_Git.m_CurrentDir, sWindowTitle);
2180 SetBackgroundImage(IDI_UPDATE_BKG);
2181 int resetTypesResource[] = { IDS_RESET_SOFT, IDS_RESET_MIXED, IDS_RESET_HARD };
2182 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_TITLE_RESET)) + _T(" ") + CString(MAKEINTRESOURCE(resetTypesResource[m_resetType])) + _T(" ") + m_revision);
2184 CAutoRepository repo(g_Git.GetGitRepository());
2185 if (!repo)
2187 ReportGitError();
2188 return false;
2191 CSmartAnimation animate(m_pAnimate);
2192 CAutoObject target;
2193 if (git_revparse_single(target.GetPointer(), repo, CUnicodeUtils::GetUTF8(m_revision)))
2194 goto error;
2195 if (git_reset(repo, target, (git_reset_t)(m_resetType + 1), nullptr, nullptr))
2196 goto error;
2198 return true;
2200 error:
2201 ReportGitError();
2202 return false;
2206 void CGitProgressList::Init()
2208 SetExtendedStyle (LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER);
2210 DeleteAllItems();
2211 int c = ((CHeaderCtrl*)(GetDlgItem(0)))->GetItemCount()-1;
2212 while (c>=0)
2213 DeleteColumn(c--);
2215 CString temp;
2216 temp.LoadString(IDS_PROGRS_ACTION);
2217 InsertColumn(0, temp);
2218 temp.LoadString(IDS_PROGRS_PATH);
2219 InsertColumn(1, temp);
2221 m_pThread = AfxBeginThread(ProgressThreadEntry, this, THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED);
2222 if (m_pThread==NULL)
2224 ReportError(CString(MAKEINTRESOURCE(IDS_ERR_THREADSTARTFAILED)));
2226 else
2228 m_pThread->m_bAutoDelete = FALSE;
2229 m_pThread->ResumeThread();
2232 // Call this early so that the column headings aren't hidden before any
2233 // text gets added.
2234 ResizeColumns();
2236 SetTimer(VISIBLETIMER, 300, NULL);
2240 void CGitProgressList::OnClose()
2242 // TODO: Add your message handler code here and/or call default
2243 if (m_bCancelled)
2245 TerminateThread(m_pThread->m_hThread, (DWORD)-1);
2246 InterlockedExchange(&m_bThreadRunning, FALSE);
2248 else
2250 m_bCancelled = TRUE;
2251 return;
2253 CListCtrl::OnClose();
2257 BOOL CGitProgressList::PreTranslateMessage(MSG* pMsg)
2259 // TODO: Add your specialized code here and/or call the base class
2260 if (pMsg->message == WM_KEYDOWN)
2262 if (pMsg->wParam == 'A')
2264 if (GetKeyState(VK_CONTROL)&0x8000)
2266 // Ctrl-A -> select all
2267 SetSelectionMark(0);
2268 for (int i=0; i<GetItemCount(); ++i)
2270 SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
2274 if ((pMsg->wParam == 'C')||(pMsg->wParam == VK_INSERT))
2276 int selIndex = GetSelectionMark();
2277 if (selIndex >= 0)
2279 if (GetKeyState(VK_CONTROL)&0x8000)
2281 //Ctrl-C -> copy to clipboard
2282 CString sClipdata;
2283 POSITION pos = GetFirstSelectedItemPosition();
2284 if (pos != NULL)
2286 while (pos)
2288 int nItem = GetNextSelectedItem(pos);
2289 CString sAction = GetItemText(nItem, 0);
2290 CString sPath = GetItemText(nItem, 1);
2291 CString sMime = GetItemText(nItem, 2);
2292 CString sLogCopyText;
2293 sLogCopyText.Format(_T("%s: %s %s\r\n"),
2294 (LPCTSTR)sAction, (LPCTSTR)sPath, (LPCTSTR)sMime);
2295 sClipdata += sLogCopyText;
2297 CStringUtils::WriteAsciiStringToClipboard(sClipdata);
2302 } // if (pMsg->message == WM_KEYDOWN)
2303 return CListCtrl::PreTranslateMessage(pMsg);
2306 CString CGitProgressList::GetPathFromColumnText(const CString& sColumnText)
2308 CString sPath = sColumnText;
2309 if (sPath.Find(':')<0)
2311 // the path is not absolute: add the common root of all paths to it
2312 sPath = g_Git.CombinePath(sColumnText);
2314 return sPath;
2317 void CGitProgressList::SetWindowTitle(UINT id, const CString& urlorpath, CString& dialogname)
2319 if (!m_bSetTitle || !m_pPostWnd)
2320 return;
2322 dialogname.LoadString(id);
2323 CAppUtils::SetWindowTitle(m_pPostWnd->GetSafeHwnd(), urlorpath, dialogname);