Update libgit2
[TortoiseGit.git] / src / TortoiseProc / GitProgressList.cpp
blob2fb63b2c3004930514ccce80cfff3f17beb8f626
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 CGitProgressList::CGitProgressList():CListCtrl()
74 , m_bCancelled(FALSE)
75 , m_pThread(NULL)
76 , m_bErrorsOccurred(false)
77 , m_bBare(false)
78 , m_bNoCheckout(false)
79 , m_AutoTag(GIT_REMOTE_DOWNLOAD_TAGS_AUTO)
80 , m_resetType(0)
81 , m_options(ProgOptNone)
82 , m_bSetTitle(false)
83 , m_pTaskbarList(nullptr)
84 , m_SendMail(nullptr)
85 , m_Command(GitProgress_none)
86 , m_AlwaysConflicted(false)
87 , m_bThreadRunning(FALSE)
88 , m_keepchangelist(false)
89 , m_nConflicts(0)
90 , m_bMergesAddsDeletesOccurred(FALSE)
91 , iFirstResized(0)
92 , bSecondResized(false)
93 , nEnsureVisibleCount(0)
94 , m_TotalBytesTransferred(0)
95 , m_bFinishedItemAdded(false)
96 , m_bLastVisible(false)
97 , m_itemCount(-1)
98 , m_itemCountTotal(-1)
100 m_pInfoCtrl = nullptr;
101 m_pAnimate = nullptr;
102 m_pProgControl = nullptr;
103 m_pProgressLabelCtrl = nullptr;
104 m_pPostWnd = nullptr;
105 m_columnbuf[0] = 0;
108 CGitProgressList::~CGitProgressList()
110 for (size_t i = 0; i < m_arData.size(); ++i)
112 delete m_arData[i];
114 if(m_pThread != NULL)
116 delete m_pThread;
121 BEGIN_MESSAGE_MAP(CGitProgressList, CListCtrl)
122 ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnNMCustomdrawSvnprogress)
123 ON_NOTIFY_REFLECT(NM_DBLCLK, OnNMDblclkSvnprogress)
124 ON_NOTIFY_REFLECT(HDN_ITEMCLICK, OnHdnItemclickSvnprogress)
125 ON_NOTIFY_REFLECT(LVN_BEGINDRAG, OnLvnBegindragSvnprogress)
126 ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnLvnGetdispinfoSvnprogress)
127 ON_MESSAGE(WM_SHOWCONFLICTRESOLVER, OnShowConflictResolver)
128 ON_WM_SIZE()
129 ON_WM_TIMER()
130 ON_WM_CONTEXTMENU()
131 ON_WM_CLOSE()
132 END_MESSAGE_MAP()
134 void CGitProgressList::Cancel()
136 m_bCancelled = TRUE;
141 // CGitProgressList message handlers
144 LRESULT CGitProgressList::OnShowConflictResolver(WPARAM /*wParam*/, LPARAM /*lParam*/)
146 #if 0
147 CConflictResolveDlg dlg(this);
148 const svn_wc_conflict_description_t *description = (svn_wc_conflict_description_t *)lParam;
149 if (description)
151 dlg.SetConflictDescription(description);
152 if (m_pTaskbarList)
154 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_PAUSED);
156 if (dlg.DoModal() == IDOK)
158 if (dlg.GetResult() == svn_wc_conflict_choose_postpone)
160 // if the result is conflicted and the dialog returned IDOK,
161 // that means we should not ask again in case of a conflict
162 m_AlwaysConflicted = true;
163 ::SendMessage(GetDlgItem(IDC_NONINTERACTIVE)->GetSafeHwnd(), BM_SETCHECK, BST_CHECKED, 0);
166 m_mergedfile = dlg.GetMergedFile();
167 m_bCancelled = dlg.IsCancelled();
168 if (m_pTaskbarList)
169 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_INDETERMINATE);
170 return dlg.GetResult();
173 return svn_wc_conflict_choose_postpone;
174 #endif
175 return 0;
177 #if 0
178 svn_wc_conflict_choice_t CGitProgressList::ConflictResolveCallback(const svn_wc_conflict_description_t *description, CString& mergedfile)
180 // we only bother the user when merging
181 if (((m_Command == GitProgress_Merge)||(m_Command == GitProgress_MergeAll)||(m_Command == GitProgress_MergeReintegrate))&&(!m_AlwaysConflicted)&&(description))
183 // we're in a worker thread here. That means we must not show a dialog from the thread
184 // but let the UI thread do it.
185 // To do that, we send a message to the UI thread and let it show the conflict resolver dialog.
186 LRESULT dlgResult = ::SendMessage(GetSafeHwnd(), WM_SHOWCONFLICTRESOLVER, 0, (LPARAM)description);
187 mergedfile = m_mergedfile;
188 return (svn_wc_conflict_choice_t)dlgResult;
191 return svn_wc_conflict_choose_postpone;
193 #endif
194 void CGitProgressList::AddItemToList()
196 int totalcount = GetItemCount();
198 SetItemCountEx(totalcount+1, LVSICF_NOSCROLL|LVSICF_NOINVALIDATEALL);
199 // make columns width fit
200 if (iFirstResized < 30)
202 // only resize the columns for the first 30 or so entries.
203 // after that, don't resize them anymore because that's an
204 // expensive function call and the columns will be sized
205 // close enough already.
206 ResizeColumns();
207 ++iFirstResized;
210 // Make sure the item is *entirely* visible even if the horizontal
211 // scroll bar is visible.
212 int count = GetCountPerPage();
213 if (totalcount <= (GetTopIndex() + count + nEnsureVisibleCount + 2))
215 ++nEnsureVisibleCount;
216 m_bLastVisible = true;
218 else
220 nEnsureVisibleCount = 0;
221 if (IsIconic() == 0)
222 m_bLastVisible = false;
227 BOOL CGitProgressList::Notify(const CTGitPath& path, git_wc_notify_action_t action
229 svn_node_kind_t kind, const CString& mime_type,
230 svn_wc_notify_state_t content_state,
231 svn_wc_notify_state_t prop_state, LONG rev,
232 const svn_lock_t * lock, svn_wc_notify_lock_state_t lock_state,
233 const CString& changelistname,
234 svn_merge_range_t * range,
235 svn_error_t * err, apr_pool_t * pool
238 bool bNoNotify = false;
239 bool bDoAddData = true;
240 NotificationData * data = new NotificationData();
241 data->path = path;
242 data->action = action;
243 data->sPathColumnText=path.GetGitPathString();
244 data->bAuxItem = false;
246 if (this->m_pAnimate)
247 this->m_pAnimate->ShowWindow(SW_HIDE);
249 #if 0
250 data->kind = kind;
251 data->mime_type = mime_type;
252 data->content_state = content_state;
253 data->prop_state = prop_state;
254 data->rev = rev;
255 data->lock_state = lock_state;
256 data->changelistname = changelistname;
257 if ((lock)&&(lock->owner))
258 data->owner = CUnicodeUtils::GetUnicode(lock->owner);
259 data->sPathColumnText = path.GetUIPathString();
260 if (!m_basePath.IsEmpty())
261 data->basepath = m_basePath;
262 if (range)
263 data->merge_range = *range;
264 #endif
265 switch (data->action)
267 case git_wc_notify_add:
268 //case svn_wc_notify_update_add:
269 // if ((data->content_state == svn_wc_notify_state_conflicted) || (data->prop_state == svn_wc_notify_state_conflicted))
270 // {
271 // data->color = m_Colors.GetColor(CColors::Conflict);
272 // data->bConflictedActionItem = true;
273 // data->sActionColumnText.LoadString(IDS_SVNACTION_CONFLICTED);
274 // ++m_nConflicts;
275 // }
276 // else
277 // {
278 // m_bMergesAddsDeletesOccurred = true;
279 data->sActionColumnText.LoadString(IDS_SVNACTION_ADD);
280 data->color = m_Colors.GetColor(CColors::Added);
281 // }
282 break;
283 case git_wc_notify_sendmail:
284 data->sActionColumnText.LoadString(IDS_SVNACTION_SENDMAIL_START);
285 data->color = m_Colors.GetColor(CColors::Modified);
286 break;
288 case git_wc_notify_resolved:
289 data->sActionColumnText.LoadString(IDS_SVNACTION_RESOLVE);
290 break;
292 case git_wc_notify_revert:
293 data->sActionColumnText.LoadString(IDS_SVNACTION_REVERT);
294 break;
296 case git_wc_notify_checkout:
297 data->sActionColumnText.LoadString(IDS_PROGRS_CMD_CHECKOUT);
298 data->color = m_Colors.GetColor(CColors::Added);
299 data->bAuxItem = false;
300 break;
302 #if 0
303 case svn_wc_notify_commit_added:
304 data->sActionColumnText.LoadString(IDS_SVNACTION_ADDING);
305 data->color = m_Colors.GetColor(CColors::Added);
306 break;
307 case svn_wc_notify_copy:
308 data->sActionColumnText.LoadString(IDS_SVNACTION_COPY);
309 break;
310 case svn_wc_notify_commit_modified:
311 data->sActionColumnText.LoadString(IDS_SVNACTION_MODIFIED);
312 data->color = m_Colors.GetColor(CColors::Modified);
313 break;
314 case svn_wc_notify_delete:
315 case svn_wc_notify_update_delete:
316 data->sActionColumnText.LoadString(IDS_SVNACTION_DELETE);
317 m_bMergesAddsDeletesOccurred = true;
318 data->color = m_Colors.GetColor(CColors::Deleted);
319 break;
320 case svn_wc_notify_commit_deleted:
321 data->sActionColumnText.LoadString(IDS_SVNACTION_DELETING);
322 data->color = m_Colors.GetColor(CColors::Deleted);
323 break;
324 case svn_wc_notify_restore:
325 data->sActionColumnText.LoadString(IDS_SVNACTION_RESTORE);
326 break;
328 case svn_wc_notify_update_replace:
329 case svn_wc_notify_commit_replaced:
330 data->sActionColumnText.LoadString(IDS_SVNACTION_REPLACED);
331 data->color = m_Colors.GetColor(CColors::Deleted);
332 break;
333 case svn_wc_notify_exists:
334 if ((data->content_state == svn_wc_notify_state_conflicted) || (data->prop_state == svn_wc_notify_state_conflicted))
336 data->color = m_Colors.GetColor(CColors::Conflict);
337 data->bConflictedActionItem = true;
338 ++m_nConflicts;
339 data->sActionColumnText.LoadString(IDS_SVNACTION_CONFLICTED);
341 else if ((data->content_state == svn_wc_notify_state_merged) || (data->prop_state == svn_wc_notify_state_merged))
343 data->color = m_Colors.GetColor(CColors::Merged);
344 m_bMergesAddsDeletesOccurred = true;
345 data->sActionColumnText.LoadString(IDS_SVNACTION_MERGED);
347 else
348 data->sActionColumnText.LoadString(IDS_SVNACTION_EXISTS);
349 break;
350 case svn_wc_notify_update_update:
351 // if this is an inoperative dir change, don't show the notification.
352 // an inoperative dir change is when a directory gets updated without
353 // any real change in either text or properties.
354 if ((kind == svn_node_dir)
355 && ((prop_state == svn_wc_notify_state_inapplicable)
356 || (prop_state == svn_wc_notify_state_unknown)
357 || (prop_state == svn_wc_notify_state_unchanged)))
359 bNoNotify = true;
360 break;
362 if ((data->content_state == svn_wc_notify_state_conflicted) || (data->prop_state == svn_wc_notify_state_conflicted))
364 data->color = m_Colors.GetColor(CColors::Conflict);
365 data->bConflictedActionItem = true;
366 ++m_nConflicts;
367 data->sActionColumnText.LoadString(IDS_SVNACTION_CONFLICTED);
369 else if ((data->content_state == svn_wc_notify_state_merged) || (data->prop_state == svn_wc_notify_state_merged))
371 data->color = m_Colors.GetColor(CColors::Merged);
372 m_bMergesAddsDeletesOccurred = true;
373 data->sActionColumnText.LoadString(IDS_SVNACTION_MERGED);
375 else if (((data->content_state != svn_wc_notify_state_unchanged)&&(data->content_state != svn_wc_notify_state_unknown)) ||
376 ((data->prop_state != svn_wc_notify_state_unchanged)&&(data->prop_state != svn_wc_notify_state_unknown)))
378 data->sActionColumnText.LoadString(IDS_SVNACTION_UPDATE);
380 else
382 bNoNotify = true;
383 break;
385 if (lock_state == svn_wc_notify_lock_state_unlocked)
387 CString temp(MAKEINTRESOURCE(IDS_SVNACTION_UNLOCKED));
388 data->sActionColumnText += _T(", ") + temp;
390 break;
392 case svn_wc_notify_update_external:
393 // For some reason we build a list of externals...
394 m_ExtStack.AddHead(path.GetUIPathString());
395 data->sActionColumnText.LoadString(IDS_SVNACTION_EXTERNAL);
396 data->bAuxItem = true;
397 break;
399 case svn_wc_notify_update_completed:
401 data->sActionColumnText.LoadString(IDS_SVNACTION_COMPLETED);
402 data->bAuxItem = true;
403 bool bEmpty = !!m_ExtStack.IsEmpty();
404 if (!bEmpty)
405 data->sPathColumnText.Format(IDS_PROGRS_PATHATREV, (LPCTSTR)m_ExtStack.RemoveHead(), rev);
406 else
407 data->sPathColumnText.Format(IDS_PROGRS_ATREV, rev);
409 if ((m_nConflicts>0)&&(bEmpty))
411 // We're going to add another aux item - let's shove this current onto the list first
412 // I don't really like this, but it will do for the moment.
413 m_arData.push_back(data);
414 AddItemToList();
416 data = new NotificationData();
417 data->bAuxItem = true;
418 data->sActionColumnText.LoadString(IDS_PROGRS_CONFLICTSOCCURED_WARNING);
419 data->sPathColumnText.LoadString(IDS_PROGRS_CONFLICTSOCCURED);
420 data->color = m_Colors.GetColor(CColors::Conflict);
421 CSoundUtils::PlayTSVNWarning();
422 // This item will now be added after the switch statement
424 if (!m_basePath.IsEmpty())
425 m_FinishedRevMap[m_basePath.GetSVNApiPath(pool)] = rev;
426 m_RevisionEnd = rev;
427 m_bFinishedItemAdded = true;
429 break;
430 case svn_wc_notify_commit_postfix_txdelta:
431 data->sActionColumnText.LoadString(IDS_SVNACTION_POSTFIX);
432 break;
433 case svn_wc_notify_failed_revert:
434 data->sActionColumnText.LoadString(IDS_SVNACTION_FAILEDREVERT);
435 break;
436 case svn_wc_notify_status_completed:
437 case svn_wc_notify_status_external:
438 data->sActionColumnText.LoadString(IDS_SVNACTION_STATUS);
439 break;
440 case svn_wc_notify_skip:
441 if ((content_state == svn_wc_notify_state_missing)||(content_state == svn_wc_notify_state_obstructed)||(content_state == svn_wc_notify_state_conflicted))
443 data->sActionColumnText.LoadString(IDS_SVNACTION_SKIPMISSING);
445 // The color settings dialog describes the red color with
446 // "possible or real conflict / obstructed" which also applies to
447 // skipped targets during a merge. So we just use the same color.
448 data->color = m_Colors.GetColor(CColors::Conflict);
450 else
451 data->sActionColumnText.LoadString(IDS_SVNACTION_SKIP);
452 break;
453 case svn_wc_notify_locked:
454 if ((lock)&&(lock->owner))
455 data->sActionColumnText.Format(IDS_SVNACTION_LOCKEDBY, (LPCTSTR)CUnicodeUtils::GetUnicode(lock->owner));
456 break;
457 case svn_wc_notify_unlocked:
458 data->sActionColumnText.LoadString(IDS_SVNACTION_UNLOCKED);
459 break;
460 case svn_wc_notify_failed_lock:
461 data->sActionColumnText.LoadString(IDS_SVNACTION_FAILEDLOCK);
462 m_arData.push_back(data);
463 AddItemToList();
464 ReportError(SVN::GetErrorString(err));
465 bDoAddData = false;
466 if (err->apr_err == SVN_ERR_FS_OUT_OF_DATE)
467 m_bLockWarning = true;
468 if (err->apr_err == SVN_ERR_FS_PATH_ALREADY_LOCKED)
469 m_bLockExists = true;
470 break;
471 case svn_wc_notify_failed_unlock:
472 data->sActionColumnText.LoadString(IDS_SVNACTION_FAILEDUNLOCK);
473 m_arData.push_back(data);
474 AddItemToList();
475 ReportError(SVN::GetErrorString(err));
476 bDoAddData = false;
477 if (err->apr_err == SVN_ERR_FS_OUT_OF_DATE)
478 m_bLockWarning = true;
479 break;
480 case svn_wc_notify_changelist_set:
481 data->sActionColumnText.Format(IDS_SVNACTION_CHANGELISTSET, (LPCTSTR)data->changelistname);
482 break;
483 case svn_wc_notify_changelist_clear:
484 data->sActionColumnText.LoadString(IDS_SVNACTION_CHANGELISTCLEAR);
485 break;
486 case svn_wc_notify_changelist_moved:
487 data->sActionColumnText.Format(IDS_SVNACTION_CHANGELISTMOVED, (LPCTSTR)data->changelistname);
488 break;
489 case svn_wc_notify_foreign_merge_begin:
490 case svn_wc_notify_merge_begin:
491 if (range == NULL)
492 data->sActionColumnText.LoadString(IDS_SVNACTION_MERGEBEGINNONE);
493 else if ((data->merge_range.start == data->merge_range.end) || (data->merge_range.start == data->merge_range.end - 1))
494 data->sActionColumnText.Format(IDS_SVNACTION_MERGEBEGINSINGLE, data->merge_range.end);
495 else if (data->merge_range.start - 1 == data->merge_range.end)
496 data->sActionColumnText.Format(IDS_SVNACTION_MERGEBEGINSINGLEREVERSE, data->merge_range.start);
497 else if (data->merge_range.start < data->merge_range.end)
498 data->sActionColumnText.Format(IDS_SVNACTION_MERGEBEGINMULTIPLE, data->merge_range.start + 1, data->merge_range.end);
499 else
500 data->sActionColumnText.Format(IDS_SVNACTION_MERGEBEGINMULTIPLEREVERSE, data->merge_range.start, data->merge_range.end + 1);
501 data->bAuxItem = true;
502 break;
503 #endif
504 default:
505 break;
506 } // switch (data->action)
508 if (bNoNotify)
509 delete data;
510 else
512 if (bDoAddData)
514 m_arData.push_back(data);
515 AddItemToList();
516 if ((!data->bAuxItem) && (m_itemCount > 0))
518 if (m_pProgControl)
520 m_pProgControl->ShowWindow(SW_SHOW);
521 m_pProgControl->SetPos(m_itemCount);
522 m_pProgControl->SetRange32(0, m_itemCountTotal);
524 if (m_pTaskbarList && m_pPostWnd)
526 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_NORMAL);
527 m_pTaskbarList->SetProgressValue(m_pPostWnd->GetSafeHwnd(), m_itemCount, m_itemCountTotal);
531 //if ((action == svn_wc_notify_commit_postfix_txdelta)&&(bSecondResized == FALSE))
533 // ResizeColumns();
534 // bSecondResized = TRUE;
538 return TRUE;
542 CString CGitProgressList::BuildInfoString()
544 CString infotext;
545 if(this->m_Command == GitProgress_Resolve)
546 infotext = _T("You need commit your change after resolve conflict");
547 #if 0
549 CString temp;
550 int added = 0;
551 int copied = 0;
552 int deleted = 0;
553 int restored = 0;
554 int reverted = 0;
555 int resolved = 0;
556 int conflicted = 0;
557 int updated = 0;
558 int merged = 0;
559 int modified = 0;
560 int skipped = 0;
561 int replaced = 0;
563 for (size_t i=0; i<m_arData.size(); ++i)
565 const NotificationData * dat = m_arData[i];
566 switch (dat->action)
568 case svn_wc_notify_add:
569 case svn_wc_notify_update_add:
570 case svn_wc_notify_commit_added:
571 if (dat->bConflictedActionItem)
572 ++conflicted;
573 else
574 ++added;
575 break;
576 case svn_wc_notify_copy:
577 ++copied;
578 break;
579 case svn_wc_notify_delete:
580 case svn_wc_notify_update_delete:
581 case svn_wc_notify_commit_deleted:
582 ++deleted;
583 break;
584 case svn_wc_notify_restore:
585 ++restored;
586 break;
587 case svn_wc_notify_revert:
588 ++reverted;
589 break;
590 case svn_wc_notify_resolved:
591 ++resolved;
592 break;
593 case svn_wc_notify_update_update:
594 if (dat->bConflictedActionItem)
595 ++conflicted;
596 else if ((dat->content_state == svn_wc_notify_state_merged) || (dat->prop_state == svn_wc_notify_state_merged))
597 ++merged;
598 else
599 ++updated;
600 break;
601 case svn_wc_notify_commit_modified:
602 ++modified;
603 break;
604 case svn_wc_notify_skip:
605 ++skipped;
606 break;
607 case svn_wc_notify_commit_replaced:
608 ++replaced;
609 break;
612 if (conflicted)
614 temp.LoadString(IDS_SVNACTION_CONFLICTED);
615 infotext += temp;
616 temp.Format(_T(":%d "), conflicted);
617 infotext += temp;
619 if (skipped)
621 temp.LoadString(IDS_SVNACTION_SKIP);
622 infotext += temp;
623 infotext.AppendFormat(_T(":%d "), skipped);
625 if (merged)
627 temp.LoadString(IDS_SVNACTION_MERGED);
628 infotext += temp;
629 infotext.AppendFormat(_T(":%d "), merged);
631 if (added)
633 temp.LoadString(IDS_SVNACTION_ADD);
634 infotext += temp;
635 infotext.AppendFormat(_T(":%d "), added);
637 if (deleted)
639 temp.LoadString(IDS_SVNACTION_DELETE);
640 infotext += temp;
641 infotext.AppendFormat(_T(":%d "), deleted);
643 if (modified)
645 temp.LoadString(IDS_SVNACTION_MODIFIED);
646 infotext += temp;
647 infotext.AppendFormat(_T(":%d "), modified);
649 if (copied)
651 temp.LoadString(IDS_SVNACTION_COPY);
652 infotext += temp;
653 infotext.AppendFormat(_T(":%d "), copied);
655 if (replaced)
657 temp.LoadString(IDS_SVNACTION_REPLACED);
658 infotext += temp;
659 infotext.AppendFormat(_T(":%d "), replaced);
661 if (updated)
663 temp.LoadString(IDS_SVNACTION_UPDATE);
664 infotext += temp;
665 infotext.AppendFormat(_T(":%d "), updated);
667 if (restored)
669 temp.LoadString(IDS_SVNACTION_RESTORE);
670 infotext += temp;
671 infotext.AppendFormat(_T(":%d "), restored);
673 if (reverted)
675 temp.LoadString(IDS_SVNACTION_REVERT);
676 infotext += temp;
677 infotext.AppendFormat(_T(":%d "), reverted);
679 if (resolved)
681 temp.LoadString(IDS_SVNACTION_RESOLVE);
682 infotext += temp;
683 infotext.AppendFormat(_T(":%d "), resolved);
685 #endif
686 return infotext;
689 void CGitProgressList::SetSelectedList(const CTGitPathList& selPaths)
691 m_selectedPaths = selPaths;
694 void CGitProgressList::ResizeColumns()
696 SetRedraw(FALSE);
698 TCHAR textbuf[MAX_PATH] = {0};
700 CHeaderCtrl * pHeaderCtrl = (CHeaderCtrl*)(GetDlgItem(0));
701 if (pHeaderCtrl)
703 int maxcol = pHeaderCtrl->GetItemCount()-1;
704 for (int col = 0; col <= maxcol; ++col)
706 // find the longest width of all items
707 int count = GetItemCount();
708 HDITEM hdi = {0};
709 hdi.mask = HDI_TEXT;
710 hdi.pszText = textbuf;
711 hdi.cchTextMax = _countof(textbuf);
712 pHeaderCtrl->GetItem(col, &hdi);
713 int cx = GetStringWidth(hdi.pszText)+20; // 20 pixels for col separator and margin
715 for (int index = 0; index<count; ++index)
717 // get the width of the string and add 12 pixels for the column separator and margins
718 int linewidth = cx;
719 switch (col)
721 case 0:
722 linewidth = GetStringWidth(m_arData[index]->sActionColumnText) + 12;
723 break;
724 case 1:
725 linewidth = GetStringWidth(m_arData[index]->sPathColumnText) + 12;
726 break;
728 if (cx < linewidth)
729 cx = linewidth;
731 SetColumnWidth(col, cx);
735 SetRedraw(TRUE);
738 bool CGitProgressList::SetBackgroundImage(UINT nID)
740 return CAppUtils::SetListCtrlBackgroundImage(GetSafeHwnd(), nID);
743 void CGitProgressList::ReportGitError()
745 ReportError(CGit::GetLibGit2LastErr());
748 void CGitProgressList::ReportUserCanceled()
750 ReportError(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED)));
753 void CGitProgressList::ReportError(const CString& sError)
755 CSoundUtils::PlayTGitError();
756 ReportString(sError, CString(MAKEINTRESOURCE(IDS_ERR_ERROR)), m_Colors.GetColor(CColors::Conflict));
757 m_bErrorsOccurred = true;
760 void CGitProgressList::ReportWarning(const CString& sWarning)
762 CSoundUtils::PlayTGitWarning();
763 ReportString(sWarning, CString(MAKEINTRESOURCE(IDS_WARN_WARNING)), m_Colors.GetColor(CColors::Conflict));
766 void CGitProgressList::ReportNotification(const CString& sNotification)
768 CSoundUtils::PlayTGitNotification();
769 ReportString(sNotification, CString(MAKEINTRESOURCE(IDS_WARN_NOTE)));
772 void CGitProgressList::ReportCmd(const CString& sCmd)
774 ReportString(sCmd, CString(MAKEINTRESOURCE(IDS_PROGRS_CMDINFO)), m_Colors.GetColor(CColors::Cmd));
777 void CGitProgressList::ReportString(CString sMessage, const CString& sMsgKind, COLORREF color)
779 // instead of showing a dialog box with the error message or notification,
780 // just insert the error text into the list control.
781 // that way the user isn't 'interrupted' by a dialog box popping up!
783 // the message may be split up into different lines
784 // so add a new entry for each line of the message
785 while (!sMessage.IsEmpty())
787 NotificationData * data = new NotificationData();
788 data->bAuxItem = true;
789 data->sActionColumnText = sMsgKind;
790 if (sMessage.Find('\n')>=0)
791 data->sPathColumnText = sMessage.Left(sMessage.Find('\n'));
792 else
793 data->sPathColumnText = sMessage;
794 data->sPathColumnText.Trim(_T("\n\r"));
795 data->color = color;
796 if (sMessage.Find('\n')>=0)
798 sMessage = sMessage.Mid(sMessage.Find('\n'));
799 sMessage.Trim(_T("\n\r"));
801 else
802 sMessage.Empty();
803 m_arData.push_back(data);
804 AddItemToList();
808 UINT CGitProgressList::ProgressThreadEntry(LPVOID pVoid)
810 return ((CGitProgressList*)pVoid)->ProgressThread();
813 UINT CGitProgressList::ProgressThread()
815 // The SetParams function should have loaded something for us
817 CString temp;
818 CString sWindowTitle;
819 bool localoperation = false;
820 bool bSuccess = false;
821 m_AlwaysConflicted = false;
823 if(m_pPostWnd)
824 m_pPostWnd->PostMessage(WM_PROG_CMD_START, m_Command);
826 if(m_pProgressLabelCtrl)
828 m_pProgressLabelCtrl->ShowWindow(SW_SHOW);
829 m_pProgressLabelCtrl->SetWindowText(_T(""));
832 // SetAndClearProgressInfo(m_hWnd);
833 m_itemCount = m_itemCountTotal;
835 InterlockedExchange(&m_bThreadRunning, TRUE);
836 iFirstResized = 0;
837 bSecondResized = FALSE;
838 m_bFinishedItemAdded = false;
839 DWORD startTime = GetCurrentTime();
841 if (m_pTaskbarList && m_pPostWnd)
842 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_INDETERMINATE);
844 switch (m_Command)
846 case GitProgress_Add:
847 bSuccess = CmdAdd(sWindowTitle, localoperation);
848 break;
849 case GitProgress_Copy:
850 bSuccess = CmdCopy(sWindowTitle, localoperation);
851 break;
852 case GitProgress_Export:
853 bSuccess = CmdExport(sWindowTitle, localoperation);
854 break;
855 case GitProgress_Rename:
856 bSuccess = CmdRename(sWindowTitle, localoperation);
857 break;
858 case GitProgress_Resolve:
859 bSuccess = CmdResolve(sWindowTitle, localoperation);
860 break;
861 case GitProgress_Revert:
862 bSuccess = CmdRevert(sWindowTitle, localoperation);
863 break;
864 case GitProgress_Switch:
865 bSuccess = CmdSwitch(sWindowTitle, localoperation);
866 break;
867 case GitProgress_SendMail:
868 bSuccess = CmdSendMail(sWindowTitle, localoperation);
869 break;
870 case GitProgress_Clone:
871 bSuccess = CmdClone(sWindowTitle, localoperation);
872 break;
873 case GitProgress_Fetch:
874 bSuccess = CmdFetch(sWindowTitle, localoperation);
875 break;
876 case GitProgress_Reset:
877 bSuccess = CmdReset(sWindowTitle, localoperation);
878 break;
880 if (!bSuccess)
881 temp.LoadString(IDS_PROGRS_TITLEFAILED);
882 else
883 temp.LoadString(IDS_PROGRS_TITLEFIN);
884 sWindowTitle = sWindowTitle + _T(" ") + temp;
885 if (m_bSetTitle && m_pPostWnd)
886 ::SetWindowText(m_pPostWnd->GetSafeHwnd(), sWindowTitle);
888 KillTimer(TRANSFERTIMER);
889 KillTimer(VISIBLETIMER);
891 if (m_pTaskbarList && m_pPostWnd)
893 if (DidErrorsOccur())
895 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_ERROR);
896 m_pTaskbarList->SetProgressValue(m_pPostWnd->GetSafeHwnd(), 100, 100);
898 else
899 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_NOPROGRESS);
902 CString info = BuildInfoString();
903 if (!bSuccess)
904 info.LoadString(IDS_PROGRS_INFOFAILED);
905 if (m_pInfoCtrl)
906 m_pInfoCtrl->SetWindowText(info);
908 ResizeColumns();
910 DWORD time = GetCurrentTime() - startTime;
912 CString sFinalInfo;
913 if (!m_sTotalBytesTransferred.IsEmpty())
915 temp.Format(IDS_PROGRS_TIME, (time / 1000) / 60, (time / 1000) % 60);
916 sFinalInfo.Format(IDS_PROGRS_FINALINFO, m_sTotalBytesTransferred, (LPCTSTR)temp);
917 if (m_pProgressLabelCtrl)
918 m_pProgressLabelCtrl->SetWindowText(sFinalInfo);
920 else
922 if (m_pProgressLabelCtrl)
923 m_pProgressLabelCtrl->ShowWindow(SW_HIDE);
926 if (m_pProgControl)
927 m_pProgControl->ShowWindow(SW_HIDE);
929 if (!m_bFinishedItemAdded)
931 CString log, str;
932 if (bSuccess)
933 str.LoadString(IDS_SUCCESS);
934 else
935 str.LoadString(IDS_FAIL);
936 log.Format(_T("%s (%lu ms @ %s)"), str, time, CLoglistUtils::FormatDateAndTime(CTime::GetCurrentTime(), DATE_SHORTDATE, true, false));
938 // there's no "finished: xxx" line at the end. We add one here to make
939 // sure the user sees that the command is actually finished.
940 ReportString(log, CString(MAKEINTRESOURCE(IDS_PROGRS_FINISHED)), bSuccess? RGB(0,0,255) : RGB(255,0,0));
943 int count = GetItemCount();
944 if ((count > 0)&&(m_bLastVisible))
945 EnsureVisible(count-1, FALSE);
947 CLogFile logfile(g_Git.m_CurrentDir);
948 if (logfile.Open())
950 logfile.AddTimeLine();
951 for (size_t i = 0; i < m_arData.size(); ++i)
953 NotificationData * data = m_arData[i];
954 temp.Format(_T("%-20s : %s"), (LPCTSTR)data->sActionColumnText, (LPCTSTR)data->sPathColumnText);
955 logfile.AddLine(temp);
957 if (!sFinalInfo.IsEmpty())
958 logfile.AddLine(sFinalInfo);
959 logfile.Close();
962 m_bCancelled = TRUE;
963 InterlockedExchange(&m_bThreadRunning, FALSE);
964 #if 0 //need
965 RefreshCursor();
966 #endif
968 if (m_pPostWnd)
969 m_pPostWnd->PostMessage(WM_PROG_CMD_FINISH, this->m_Command, 0L);
971 #if 0
972 DWORD dwAutoClose = CRegStdDWORD(_T("Software\\TortoiseGit\\AutoClose"), CLOSE_MANUAL);
973 if (m_options & ProgOptDryRun)
974 dwAutoClose = 0; // dry run means progress dialog doesn't auto close at all
975 if (!m_bLastVisible)
976 dwAutoClose = 0;
977 if (m_dwCloseOnEnd != (DWORD)-1)
978 dwAutoClose = m_dwCloseOnEnd; // command line value has priority over setting value
979 if ((dwAutoClose == CLOSE_NOERRORS)&&(!m_bErrorsOccurred))
980 PostMessage(WM_COMMAND, 1, (LPARAM)GetDlgItem(IDOK)->m_hWnd);
981 if ((dwAutoClose == CLOSE_NOCONFLICTS)&&(!m_bErrorsOccurred)&&(m_nConflicts==0))
982 PostMessage(WM_COMMAND, 1, (LPARAM)GetDlgItem(IDOK)->m_hWnd);
983 if ((dwAutoClose == CLOSE_NOMERGES)&&(!m_bErrorsOccurred)&&(m_nConflicts==0)&&(!m_bMergesAddsDeletesOccurred))
984 PostMessage(WM_COMMAND, 1, (LPARAM)GetDlgItem(IDOK)->m_hWnd);
985 if ((dwAutoClose == CLOSE_LOCAL)&&(!m_bErrorsOccurred)&&(m_nConflicts==0)&&(localoperation))
986 PostMessage(WM_COMMAND, 1, (LPARAM)GetDlgItem(IDOK)->m_hWnd);
987 #endif
989 //Don't do anything here which might cause messages to be sent to the window
990 //The window thread is probably now blocked in OnOK if we've done an auto close
991 return 0;
994 void CGitProgressList::OnLvnGetdispinfoSvnprogress(NMHDR *pNMHDR, LRESULT *pResult)
996 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
998 if (pDispInfo)
1000 if (pDispInfo->item.mask & LVIF_TEXT)
1002 if (pDispInfo->item.iItem < (int)m_arData.size())
1004 const NotificationData * data = m_arData[pDispInfo->item.iItem];
1005 switch (pDispInfo->item.iSubItem)
1007 case 0:
1008 lstrcpyn(m_columnbuf, data->sActionColumnText, MAX_PATH);
1009 break;
1010 case 1:
1011 lstrcpyn(m_columnbuf, data->sPathColumnText, pDispInfo->item.cchTextMax);
1012 if (!data->bAuxItem)
1014 int cWidth = GetColumnWidth(1);
1015 cWidth = max(12, cWidth-12);
1016 CDC * pDC = GetDC();
1017 if (pDC != NULL)
1019 CFont * pFont = pDC->SelectObject(GetFont());
1020 PathCompactPath(pDC->GetSafeHdc(), m_columnbuf, cWidth);
1021 pDC->SelectObject(pFont);
1022 ReleaseDC(pDC);
1025 break;
1026 default:
1027 m_columnbuf[0] = 0;
1029 pDispInfo->item.pszText = m_columnbuf;
1033 *pResult = 0;
1036 void CGitProgressList::OnNMCustomdrawSvnprogress(NMHDR *pNMHDR, LRESULT *pResult)
1038 NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );
1040 // Take the default processing unless we set this to something else below.
1041 *pResult = CDRF_DODEFAULT;
1043 // First thing - check the draw stage. If it's the control's prepaint
1044 // stage, then tell Windows we want messages for every item.
1046 if ( CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage )
1048 *pResult = CDRF_NOTIFYITEMDRAW;
1050 else if ( CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage )
1052 // This is the prepaint stage for an item. Here's where we set the
1053 // item's text color. Our return value will tell Windows to draw the
1054 // item itself, but it will use the new color we set here.
1056 // Tell Windows to paint the control itself.
1057 *pResult = CDRF_DODEFAULT;
1059 ASSERT(pLVCD->nmcd.dwItemSpec < m_arData.size());
1060 if(pLVCD->nmcd.dwItemSpec >= m_arData.size())
1062 return;
1064 const NotificationData * data = m_arData[pLVCD->nmcd.dwItemSpec];
1065 ASSERT(data != NULL);
1066 if (data == NULL)
1067 return;
1069 // Store the color back in the NMLVCUSTOMDRAW struct.
1070 pLVCD->clrText = data->color;
1074 void CGitProgressList::OnNMDblclkSvnprogress(NMHDR * /*pNMHDR*/, LRESULT * /*pResult*/)
1076 #if 0
1077 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1078 *pResult = 0;
1079 if (pNMLV->iItem < 0)
1080 return;
1081 if (m_options & ProgOptDryRun)
1082 return; //don't do anything in a dry-run.
1084 const NotificationData * data = m_arData[pNMLV->iItem];
1085 if (data == NULL)
1086 return;
1088 if (data->bConflictedActionItem)
1090 // We've double-clicked on a conflicted item - do a three-way merge on it
1091 SVNDiff::StartConflictEditor(data->path);
1093 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))
1095 // This is a modified file which has been merged on update. Diff it against base
1096 CTGitPath temporaryFile;
1097 SVNDiff diff(this, this->m_hWnd, true);
1098 diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1099 svn_revnum_t baseRev = 0;
1100 diff.DiffFileAgainstBase(data->path, baseRev);
1102 else if ((!data->bAuxItem)&&(data->path.Exists())&&(!data->path.IsDirectory()))
1104 bool bOpenWith = false;
1105 int ret = (int)ShellExecute(m_hWnd, NULL, data->path.GetWinPath(), NULL, NULL, SW_SHOWNORMAL);
1106 if (ret <= HINSTANCE_ERROR)
1107 bOpenWith = true;
1108 if (bOpenWith)
1110 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1111 cmd += data->path.GetWinPathString() + _T(" ");
1112 CAppUtils::LaunchApplication(cmd, NULL, false);
1115 #endif
1118 void CGitProgressList::OnHdnItemclickSvnprogress(NMHDR *pNMHDR, LRESULT *pResult)
1120 LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
1121 if (m_bThreadRunning)
1122 return;
1123 if (m_nSortedColumn == phdr->iItem)
1124 m_bAscending = !m_bAscending;
1125 else
1126 m_bAscending = TRUE;
1127 m_nSortedColumn = phdr->iItem;
1128 Sort();
1130 CString temp;
1131 SetRedraw(FALSE);
1132 DeleteAllItems();
1133 SetItemCountEx (static_cast<int>(m_arData.size()));
1135 SetRedraw(TRUE);
1137 *pResult = 0;
1140 bool CGitProgressList::NotificationDataIsAux(const NotificationData* pData)
1142 return pData->bAuxItem;
1144 BOOL CGitProgressList::Notify(const git_wc_notify_action_t action, CString str, const git_oid *a, const git_oid *b)
1146 NotificationData * data = new NotificationData();
1147 data->action = action;
1148 data->bAuxItem = false;
1150 if (action == git_wc_notify_update_ref)
1152 data->m_NewHash = b->id;
1153 data->m_OldHash = a->id;
1154 data->sActionColumnText.LoadString(IDS_GITACTION_UPDATE_REF);
1155 data->sPathColumnText.Format(_T("%s\t %s -> %s"), str,
1156 data->m_OldHash.ToString().Left(g_Git.GetShortHASHLength()),
1157 data->m_NewHash.ToString().Left(g_Git.GetShortHASHLength()));
1161 m_arData.push_back(data);
1162 AddItemToList();
1164 if (m_pAnimate)
1165 m_pAnimate->Stop();
1166 if (m_pAnimate)
1167 m_pAnimate->ShowWindow(SW_HIDE);
1168 return TRUE;
1170 BOOL CGitProgressList::Notify(const git_wc_notify_action_t /*action*/, const git_transfer_progress *stat)
1172 static unsigned int start = 0;
1173 unsigned int dt = GetCurrentTime() - start;
1174 double speed = 0;
1176 if (m_bCancelled)
1177 return FALSE;
1179 if (dt > 100)
1181 start = GetCurrentTime();
1182 size_t ds = stat->received_bytes - m_TotalBytesTransferred;
1183 speed = ds * 1000.0/dt;
1184 m_TotalBytesTransferred = stat->received_bytes;
1186 else
1188 return TRUE;
1191 int progress;
1192 progress = stat->received_objects + stat->indexed_objects;
1194 if ((stat->total_objects > 1000) && m_pProgControl && (!m_pProgControl->IsWindowVisible()))
1196 if (m_pProgControl)
1197 m_pProgControl->ShowWindow(SW_SHOW);
1200 if (m_pProgressLabelCtrl && m_pProgressLabelCtrl->IsWindowVisible())
1201 m_pProgressLabelCtrl->ShowWindow(SW_SHOW);
1203 if (m_pProgControl)
1205 m_pProgControl->SetPos(progress);
1206 m_pProgControl->SetRange32(0, 2 * stat->total_objects);
1208 if (m_pTaskbarList && m_pPostWnd)
1210 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_NORMAL);
1211 m_pTaskbarList->SetProgressValue(m_pPostWnd->GetSafeHwnd(), progress, 2 * stat->total_objects);
1214 CString progText;
1215 if (stat->received_bytes < 1024)
1216 m_sTotalBytesTransferred.Format(IDS_SVN_PROGRESS_TOTALBYTESTRANSFERRED, (int64_t)stat->received_bytes);
1217 else if (stat->received_bytes < 1200000)
1218 m_sTotalBytesTransferred.Format(IDS_SVN_PROGRESS_TOTALTRANSFERRED, (int64_t)stat->received_bytes / 1024);
1219 else
1220 m_sTotalBytesTransferred.Format(IDS_SVN_PROGRESS_TOTALMBTRANSFERRED, (double)((double)stat->received_bytes / 1024000.0));
1222 CString str;
1223 if(speed < 1024)
1224 str.Format(_T("%fB/s"), speed);
1225 else if(speed < 1024 * 1024)
1226 str.Format(_T("%.2fKB/s"), speed / 1024);
1227 else
1228 str.Format(_T("%.2fMB/s"), speed / 1024000.0);
1230 progText.Format(IDS_SVN_PROGRESS_TOTALANDSPEED, (LPCTSTR)m_sTotalBytesTransferred, (LPCTSTR)str);
1231 if (m_pProgressLabelCtrl)
1232 m_pProgressLabelCtrl->SetWindowText(progText);
1234 return TRUE;
1237 void CGitProgressList::OnTimer(UINT_PTR nIDEvent)
1239 if (nIDEvent == TRANSFERTIMER)
1241 CString progText;
1242 CString progSpeed;
1243 progSpeed.Format(IDS_SVN_PROGRESS_BYTES_SEC, 0);
1244 progText.Format(IDS_SVN_PROGRESS_TOTALANDSPEED, (LPCTSTR)m_sTotalBytesTransferred, (LPCTSTR)progSpeed);
1245 if (m_pProgressLabelCtrl)
1246 m_pProgressLabelCtrl->SetWindowText(progText);
1248 KillTimer(TRANSFERTIMER);
1250 if (nIDEvent == VISIBLETIMER)
1252 if (nEnsureVisibleCount)
1253 EnsureVisible(GetItemCount()-1, false);
1254 nEnsureVisibleCount = 0;
1258 void CGitProgressList::Sort()
1260 if(m_arData.size() < 2)
1262 return;
1265 // We need to sort the blocks which lie between the auxiliary entries
1266 // This is so that any aux data stays where it was
1267 NotificationDataVect::iterator actionBlockBegin;
1268 NotificationDataVect::iterator actionBlockEnd = m_arData.begin(); // We start searching from here
1270 for(;;)
1272 // Search to the start of the non-aux entry in the next block
1273 actionBlockBegin = std::find_if(actionBlockEnd, m_arData.end(), std::not1(std::ptr_fun(&CGitProgressList::NotificationDataIsAux)));
1274 if(actionBlockBegin == m_arData.end())
1276 // There are no more actions
1277 break;
1279 // Now search to find the end of the block
1280 actionBlockEnd = std::find_if(actionBlockBegin+1, m_arData.end(), std::ptr_fun(&CGitProgressList::NotificationDataIsAux));
1281 // Now sort the block
1282 std::sort(actionBlockBegin, actionBlockEnd, &CGitProgressList::SortCompare);
1286 bool CGitProgressList::SortCompare(const NotificationData * pData1, const NotificationData * pData2)
1288 int result = 0;
1289 switch (m_nSortedColumn)
1291 case 0: //action column
1292 result = pData1->sActionColumnText.Compare(pData2->sActionColumnText);
1293 break;
1294 case 1: //path column
1295 // Compare happens after switch()
1296 break;
1297 default:
1298 break;
1301 // Sort by path if everything else is equal
1302 if (result == 0)
1304 result = CTGitPath::Compare(pData1->path, pData2->path);
1307 if (!m_bAscending)
1308 result = -result;
1309 return result < 0;
1312 void CGitProgressList::OnContextMenu(CWnd* pWnd, CPoint point)
1314 if (m_options & ProgOptDryRun)
1315 return; // don't do anything in a dry-run.
1317 if (pWnd == this)
1319 int selIndex = GetSelectionMark();
1320 if ((point.x == -1) && (point.y == -1))
1322 // Menu was invoked from the keyboard rather than by right-clicking
1323 CRect rect;
1324 GetItemRect(selIndex, &rect, LVIR_LABEL);
1325 ClientToScreen(&rect);
1326 point = rect.CenterPoint();
1329 if ((selIndex >= 0)&&(!m_bThreadRunning))
1331 // entry is selected, thread has finished with updating so show the popup menu
1332 CIconMenu popup;
1333 if (popup.CreatePopupMenu())
1335 bool bAdded = false;
1336 NotificationData * data = m_arData[selIndex];
1337 if ((data)&&(!data->path.IsDirectory()))
1340 if (data->action == svn_wc_notify_update_update || data->action == svn_wc_notify_resolved)
1342 if (GetSelectedCount() == 1)
1344 popup.AppendMenuIcon(ID_COMPARE, IDS_LOG_POPUP_COMPARE, IDI_DIFF);
1345 bAdded = true;
1349 if (data->bConflictedActionItem)
1351 if (GetSelectedCount() == 1)
1353 popup.AppendMenuIcon(ID_EDITCONFLICT, IDS_MENUCONFLICT,IDI_CONFLICT);
1354 popup.SetDefaultItem(ID_EDITCONFLICT, FALSE);
1355 popup.AppendMenuIcon(ID_CONFLICTRESOLVE, IDS_SVNPROGRESS_MENUMARKASRESOLVED,IDI_RESOLVE);
1357 popup.AppendMenuIcon(ID_CONFLICTUSETHEIRS, IDS_SVNPROGRESS_MENUUSETHEIRS,IDI_RESOLVE);
1358 popup.AppendMenuIcon(ID_CONFLICTUSEMINE, IDS_SVNPROGRESS_MENUUSEMINE,IDI_RESOLVE);
1360 else if ((data->content_state == svn_wc_notify_state_merged)||(GitProgress_Merge == m_Command)||(data->action == svn_wc_notify_resolved))
1361 popup.SetDefaultItem(ID_COMPARE, FALSE);
1363 if (GetSelectedCount() == 1)
1365 if ((data->action == git_wc_notify_add)||
1366 (data->action == git_wc_notify_revert)||
1367 (data->action == git_wc_notify_resolved)||
1368 (data->action == git_wc_notify_checkout)||
1369 (data->action == git_wc_notify_update_ref))
1371 popup.AppendMenuIcon(ID_LOG, IDS_MENULOG,IDI_LOG);
1372 popup.AppendMenu(MF_SEPARATOR, NULL);
1373 popup.AppendMenuIcon(ID_OPEN, IDS_LOG_POPUP_OPEN, IDI_OPEN);
1374 popup.AppendMenuIcon(ID_OPENWITH, IDS_LOG_POPUP_OPENWITH, IDI_OPEN);
1375 bAdded = true;
1378 } // if ((data)&&(!data->path.IsDirectory()))
1379 if (GetSelectedCount() == 1)
1381 if (data)
1383 CString sPath = GetPathFromColumnText(data->sPathColumnText);
1384 CTGitPath path = CTGitPath(sPath);
1385 if (!sPath.IsEmpty())
1387 if (path.GetDirectory().Exists())
1389 popup.AppendMenuIcon(ID_EXPLORE, IDS_SVNPROGRESS_MENUOPENPARENT, IDI_EXPLORER);
1390 bAdded = true;
1395 if (GetSelectedCount() > 0)
1397 if (bAdded)
1398 popup.AppendMenu(MF_SEPARATOR, NULL);
1399 popup.AppendMenuIcon(ID_COPY, IDS_LOG_POPUP_COPYTOCLIPBOARD,IDI_COPYCLIP);
1400 bAdded = true;
1402 if (bAdded)
1404 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
1405 #if 0//need
1406 DialogEnableWindow(IDOK, FALSE);
1407 #endif
1408 //this->SetPromptApp(&theApp);
1409 theApp.DoWaitCursor(1);
1410 bool bOpenWith = false;
1411 switch (cmd)
1413 case ID_COPY:
1415 CString sLines;
1416 POSITION pos = GetFirstSelectedItemPosition();
1417 while (pos)
1419 int nItem = GetNextSelectedItem(pos);
1420 NotificationData * data = m_arData[nItem];
1421 if (data)
1423 sLines += data->sPathColumnText;
1424 sLines += _T("\r\n");
1427 sLines.TrimRight();
1428 if (!sLines.IsEmpty())
1430 CStringUtils::WriteAsciiStringToClipboard(sLines, GetSafeHwnd());
1433 break;
1434 case ID_EXPLORE:
1436 if (data)
1438 CString sPath = GetPathFromColumnText(data->sPathColumnText);
1440 CTGitPath path = CTGitPath(sPath);
1441 ShellExecute(m_hWnd, _T("explore"), path.GetDirectory().GetWinPath(), NULL, path.GetDirectory().GetWinPath(), SW_SHOW);
1444 break;
1445 #if 0
1446 case ID_COMPARE:
1448 svn_revnum_t rev = -1;
1449 StringRevMap::iterator it = m_UpdateStartRevMap.end();
1450 if (data->basepath.IsEmpty())
1451 it = m_UpdateStartRevMap.begin();
1452 else
1453 it = m_UpdateStartRevMap.find(data->basepath.GetSVNApiPath(pool));
1454 if (it != m_UpdateStartRevMap.end())
1455 rev = it->second;
1456 // if the file was merged during update, do a three way diff between OLD, MINE, THEIRS
1457 if (data->content_state == svn_wc_notify_state_merged)
1459 CTGitPath basefile = CTempFiles::Instance().GetTempFilePath(false, data->path, rev);
1460 CTGitPath newfile = CTempFiles::Instance().GetTempFilePath(false, data->path, SVNRev::REV_HEAD);
1461 SVN svn;
1462 if (!svn.Cat(data->path, SVNRev(SVNRev::REV_WC), rev, basefile))
1464 CMessageBox::Show(m_hWnd, svn.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1465 DialogEnableWindow(IDOK, TRUE);
1466 break;
1468 // If necessary, convert the line-endings on the file before diffing
1469 if ((DWORD)CRegDWORD(_T("Software\\TortoiseGit\\ConvertBase"), TRUE))
1471 CTGitPath temporaryFile = CTempFiles::Instance().GetTempFilePath(false, data->path, SVNRev::REV_BASE);
1472 if (!svn.Cat(data->path, SVNRev(SVNRev::REV_BASE), SVNRev(SVNRev::REV_BASE), temporaryFile))
1474 temporaryFile.Reset();
1475 break;
1477 else
1479 newfile = temporaryFile;
1483 SetFileAttributes(newfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1484 SetFileAttributes(basefile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1485 CString revname, wcname, basename;
1486 revname.Format(_T("%s Revision %ld"), (LPCTSTR)data->path.GetUIFileOrDirectoryName(), rev);
1487 wcname.Format(IDS_DIFF_WCNAME, (LPCTSTR)data->path.GetUIFileOrDirectoryName());
1488 basename.Format(IDS_DIFF_BASENAME, (LPCTSTR)data->path.GetUIFileOrDirectoryName());
1489 CAppUtils::StartExtMerge(basefile, newfile, data->path, data->path, basename, revname, wcname, CString(), true);
1491 else
1493 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, data->path, rev);
1494 SVN svn;
1495 if (!svn.Cat(data->path, SVNRev(SVNRev::REV_WC), rev, tempfile))
1497 CMessageBox::Show(m_hWnd, svn.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1498 DialogEnableWindow(IDOK, TRUE);
1499 break;
1501 else
1503 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1504 CString revname, wcname;
1505 revname.Format(_T("%s Revision %ld"), (LPCTSTR)data->path.GetUIFileOrDirectoryName(), rev);
1506 wcname.Format(IDS_DIFF_WCNAME, (LPCTSTR)data->path.GetUIFileOrDirectoryName());
1507 CAppUtils::StartExtDiff(
1508 tempfile, data->path, revname, wcname,
1509 CAppUtils::DiffFlags().AlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000)));
1513 break;
1514 case ID_EDITCONFLICT:
1516 CString sPath = GetPathFromColumnText(data->sPathColumnText);
1517 SVNDiff::StartConflictEditor(CTGitPath(sPath));
1519 break;
1520 case ID_CONFLICTUSETHEIRS:
1521 case ID_CONFLICTUSEMINE:
1522 case ID_CONFLICTRESOLVE:
1524 svn_wc_conflict_choice_t result = svn_wc_conflict_choose_merged;
1525 switch (cmd)
1527 case ID_CONFLICTUSETHEIRS:
1528 result = svn_wc_conflict_choose_theirs_full;
1529 break;
1530 case ID_CONFLICTUSEMINE:
1531 result = svn_wc_conflict_choose_mine_full;
1532 break;
1533 case ID_CONFLICTRESOLVE:
1534 result = svn_wc_conflict_choose_merged;
1535 break;
1537 SVN svn;
1538 POSITION pos = GetFirstSelectedItemPosition();
1539 CString sResolvedPaths;
1540 while (pos)
1542 int nItem = GetNextSelectedItem(pos);
1543 NotificationData * data = m_arData[nItem];
1544 if (data)
1546 if (data->bConflictedActionItem)
1548 if (!svn.Resolve(data->path, result, FALSE))
1550 CMessageBox::Show(m_hWnd, svn.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1551 DialogEnableWindow(IDOK, TRUE);
1552 break;
1554 else
1556 data->color = ::GetSysColor(COLOR_WINDOWTEXT);
1557 data->action = svn_wc_notify_resolved;
1558 data->sActionColumnText.LoadString(IDS_SVNACTION_RESOLVE);
1559 data->bConflictedActionItem = false;
1560 m_nConflicts--;
1562 if (m_nConflicts==0)
1564 // When the last conflict is resolved we remove
1565 // the warning which we assume is in the last line.
1566 int nIndex = GetItemCount()-1;
1567 VERIFY(DeleteItem(nIndex));
1569 delete m_arData[nIndex];
1570 m_arData.pop_back();
1572 sResolvedPaths += data->path.GetWinPathString() + _T("\n");
1577 Invalidate();
1578 CString info = BuildInfoString();
1579 SetDlgItemText(IDC_INFOTEXT, info);
1581 if (!sResolvedPaths.IsEmpty())
1583 CString msg;
1584 msg.Format(IDS_SVNPROGRESS_RESOLVED, (LPCTSTR)sResolvedPaths);
1585 CMessageBox::Show(m_hWnd, msg, _T("TortoiseGit"), MB_OK | MB_ICONINFORMATION);
1588 break;
1589 #endif
1590 case ID_LOG:
1592 if (!data)
1593 return;
1594 CString cmd = _T("/command:log");
1595 CString sPath = GetPathFromColumnText(data->sPathColumnText);
1596 if(data->action == git_wc_notify_update_ref)
1598 cmd += _T(" /path:\"") + GetPathFromColumnText(CString()) + _T("\"");
1599 if (!data->m_OldHash.IsEmpty())
1600 cmd += _T(" /startrev:") + data->m_OldHash.ToString();
1601 if (!data->m_NewHash.IsEmpty())
1602 cmd += _T(" /endrev:") + data->m_NewHash.ToString();
1604 else
1605 cmd += _T(" /path:\"") + sPath + _T("\"");
1606 CAppUtils::RunTortoiseGitProc(cmd);
1608 break;
1609 case ID_OPENWITH:
1610 bOpenWith = true;
1611 case ID_OPEN:
1613 if (!data)
1614 return;
1615 int ret = 0;
1616 CString sWinPath = GetPathFromColumnText(data->sPathColumnText);
1617 if (!bOpenWith)
1618 ret = (int)ShellExecute(this->m_hWnd, NULL, (LPCTSTR)sWinPath, NULL, NULL, SW_SHOWNORMAL);
1619 if ((ret <= HINSTANCE_ERROR)||bOpenWith)
1621 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1622 cmd += sWinPath + _T(" ");
1623 CAppUtils::LaunchApplication(cmd, NULL, false);
1627 #if 0 //need
1628 DialogEnableWindow(IDOK, TRUE);
1629 #endif
1630 theApp.DoWaitCursor(-1);
1631 } // if (bAdded)
1637 void CGitProgressList::OnLvnBegindragSvnprogress(NMHDR* , LRESULT *pResult)
1639 //LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1640 #if 0
1641 int selIndex = GetSelectionMark();
1642 if (selIndex < 0)
1643 return;
1645 CDropFiles dropFiles; // class for creating DROPFILES struct
1647 int index;
1648 POSITION pos = GetFirstSelectedItemPosition();
1649 while ( (index = GetNextSelectedItem(pos)) >= 0 )
1651 NotificationData * data = m_arData[index];
1653 if ( data->kind==svn_node_file || data->kind==svn_node_dir )
1655 CString sPath = GetPathFromColumnText(data->sPathColumnText);
1657 dropFiles.AddFile( sPath );
1661 if (!dropFiles.IsEmpty())
1663 dropFiles.CreateStructure();
1665 #endif
1666 *pResult = 0;
1669 void CGitProgressList::OnSize(UINT nType, int cx, int cy)
1671 CListCtrl::OnSize(nType, cx, cy);
1672 if ((nType == SIZE_RESTORED)&&(m_bLastVisible))
1674 if(!m_hWnd)
1675 return;
1677 int count = GetItemCount();
1678 if (count > 0)
1679 EnsureVisible(count-1, false);
1683 //////////////////////////////////////////////////////////////////////////
1684 /// commands
1685 //////////////////////////////////////////////////////////////////////////
1686 bool CGitProgressList::CmdAdd(CString& sWindowTitle, bool& localoperation)
1688 localoperation = true;
1689 SetWindowTitle(IDS_PROGRS_TITLE_ADD, g_Git.m_CurrentDir + _T("\\") + m_targetPathList.GetCommonRoot().GetUIPathString(), sWindowTitle);
1690 SetBackgroundImage(IDI_ADD_BKG);
1691 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_ADD)));
1693 m_itemCountTotal = m_targetPathList.GetCount();
1695 if (CRegDWORD(_T("Software\\TortoiseGit\\UseLibgit2"), TRUE) == TRUE)
1697 git_repository *repo = NULL;
1698 git_index *index;
1700 CStringA gitdir = CUnicodeUtils::GetMulti(CTGitPath(g_Git.m_CurrentDir).GetGitPathString(), CP_UTF8);
1701 if (git_repository_open(&repo, gitdir))
1703 ReportGitError();
1704 return false;
1707 if (git_repository_index(&index, repo))
1709 ReportGitError();
1710 git_repository_free(repo);
1711 return false;
1713 if (git_index_read(index, true))
1715 ReportGitError();
1716 git_index_free(index);
1717 git_repository_free(repo);
1718 return false;
1721 for (m_itemCount = 0; m_itemCount < m_itemCountTotal; ++m_itemCount)
1723 if (git_index_add_bypath(index, CUnicodeUtils::GetMulti(m_targetPathList[m_itemCount].GetGitPathString(), CP_UTF8)))
1725 ReportGitError();
1726 git_index_free(index);
1727 git_repository_free(repo);
1728 return false;
1730 Notify(m_targetPathList[m_itemCount], git_wc_notify_add);
1732 if (IsCancelled() == TRUE)
1734 git_index_free(index);
1735 git_repository_free(repo);
1737 ReportUserCanceled();
1738 return false;
1742 if (git_index_write(index))
1744 ReportGitError();
1745 git_index_free(index);
1746 git_repository_free(repo);
1747 return false;
1750 git_index_free(index);
1751 git_repository_free(repo);
1753 else
1755 CMassiveGitTask mgt(L"add -f");
1756 if (!mgt.ExecuteWithNotify(&m_targetPathList, m_bCancelled, git_wc_notify_add, this))
1757 return false;
1760 CShellUpdater::Instance().AddPathsForUpdate(m_targetPathList);
1762 return true;
1765 bool CGitProgressList::CmdCopy(CString& /*sWindowTitle*/, bool& /*localoperation*/)
1767 #if 0
1768 ASSERT(m_targetPathList.GetCount() == 1);
1769 sWindowTitle.LoadString(IDS_PROGRS_TITLE_COPY);
1770 SetWindowText(sWindowTitle); // needs to be updated, see TSVN rev. 21375
1771 SetBackgroundImage(IDI_COPY_BKG);
1773 CString sCmdInfo;
1774 sCmdInfo.Format(IDS_PROGRS_CMD_COPY,
1775 m_targetPathList[0].IsUrl() ? (LPCTSTR)m_targetPathList[0].GetSVNPathString() : m_targetPathList[0].GetWinPath(),
1776 (LPCTSTR)m_url.GetSVNPathString(), (LPCTSTR)m_Revision.ToString());
1777 ReportCmd(sCmdInfo);
1779 if (!Copy(m_targetPathList, m_url, m_Revision, m_pegRev, m_sMessage))
1781 ReportSVNError();
1782 return false;
1784 if (m_options & ProgOptSwitchAfterCopy)
1786 sCmdInfo.Format(IDS_PROGRS_CMD_SWITCH,
1787 m_targetPathList[0].GetWinPath(),
1788 (LPCTSTR)m_url.GetSVNPathString(), (LPCTSTR)m_Revision.ToString());
1789 ReportCmd(sCmdInfo);
1790 if (!Switch(m_targetPathList[0], m_url, SVNRev::REV_HEAD, SVNRev::REV_HEAD, m_depth, TRUE, m_options & ProgOptIgnoreExternals))
1792 if (!Switch(m_targetPathList[0], m_url, SVNRev::REV_HEAD, m_Revision, m_depth, TRUE, m_options & ProgOptIgnoreExternals))
1794 ReportSVNError();
1795 return false;
1799 else
1801 if (SVN::PathIsURL(m_url))
1803 CString sMsg(MAKEINTRESOURCE(IDS_PROGRS_COPY_WARNING));
1804 ReportNotification(sMsg);
1807 #endif
1808 return true;
1811 bool CGitProgressList::CmdExport(CString& /*sWindowTitle*/, bool& /*localoperation*/)
1813 #if 0
1814 ASSERT(m_targetPathList.GetCount() == 1);
1815 sWindowTitle.LoadString(IDS_PROGRS_TITLE_EXPORT);
1816 sWindowTitle = m_url.GetUIFileOrDirectoryName()+_T(" - ")+sWindowTitle;
1817 SetWindowText(sWindowTitle); // needs to be updated, see TSVN rev. 21375
1818 SetBackgroundImage(IDI_EXPORT_BKG);
1819 CString eol;
1820 if (m_options & ProgOptEolCRLF)
1821 eol = _T("CRLF");
1822 if (m_options & ProgOptEolLF)
1823 eol = _T("LF");
1824 if (m_options & ProgOptEolCR)
1825 eol = _T("CR");
1826 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_EXPORT)));
1827 if (!Export(m_url, m_targetPathList[0], m_Revision, m_Revision, TRUE, m_options & ProgOptIgnoreExternals, m_depth, NULL, FALSE, eol))
1829 ReportSVNError();
1830 return false;
1832 #endif
1833 return true;
1836 bool CGitProgressList::CmdRename(CString& /*sWindowTitle*/, bool& /*localoperation*/)
1838 #if 0
1839 ASSERT(m_targetPathList.GetCount() == 1);
1840 if ((!m_targetPathList[0].IsUrl())&&(!m_url.IsUrl()))
1841 localoperation = true;
1842 sWindowTitle.LoadString(IDS_PROGRS_TITLE_RENAME);
1843 SetWindowText(sWindowTitle); // needs to be updated, see TSVN rev. 21375
1844 SetBackgroundImage(IDI_RENAME_BKG);
1845 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_RENAME)));
1846 if (!Move(m_targetPathList, m_url, m_Revision, m_sMessage))
1848 ReportSVNError();
1849 return false;
1851 #endif
1852 return true;
1855 bool CGitProgressList::CmdResolve(CString& sWindowTitle, bool& localoperation)
1858 localoperation = true;
1859 ASSERT(m_targetPathList.GetCount() == 1);
1860 SetWindowTitle(IDS_PROGRS_TITLE_RESOLVE, g_Git.m_CurrentDir + _T("\\") + m_targetPathList.GetCommonRoot().GetUIPathString(), sWindowTitle);
1861 SetBackgroundImage(IDI_RESOLVE_BKG);
1862 // check if the file may still have conflict markers in it.
1863 //BOOL bMarkers = FALSE;
1865 m_itemCountTotal = m_targetPathList.GetCount();
1866 for (m_itemCount = 0; m_itemCount < m_itemCountTotal; ++m_itemCount)
1868 CString cmd,out,tempmergefile;
1869 cmd.Format(_T("git.exe add -f -- \"%s\""),m_targetPathList[m_itemCount].GetGitPathString());
1870 if (g_Git.Run(cmd, &out, CP_UTF8))
1872 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK|MB_ICONERROR);
1873 m_bErrorsOccurred=true;
1874 return false;
1877 CAppUtils::RemoveTempMergeFile((CTGitPath &)m_targetPathList[m_itemCount]);
1879 Notify(m_targetPathList[m_itemCount], git_wc_notify_resolved);
1881 #if 0
1882 if ((m_options & ProgOptSkipConflictCheck) == 0)
1886 for (INT_PTR fileindex=0; (fileindex<m_targetPathList.GetCount()) && (bMarkers==FALSE); ++fileindex)
1888 if (!m_targetPathList[fileindex].IsDirectory())
1890 CStdioFile file(m_targetPathList[fileindex].GetWinPath(), CFile::typeBinary | CFile::modeRead);
1891 CString strLine = _T("");
1892 while (file.ReadString(strLine))
1894 if (strLine.Find(_T("<<<<<<<"))==0)
1896 bMarkers = TRUE;
1897 break;
1900 file.Close();
1904 catch (CFileException* pE)
1906 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CFileException in Resolve!\n");
1907 TCHAR error[10000] = {0};
1908 pE->GetErrorMessage(error, 10000);
1909 ReportError(error);
1910 pE->Delete();
1911 return false;
1914 if (bMarkers)
1916 if (CMessageBox::Show(m_hWnd, IDS_PROGRS_REVERTMARKERS, IDS_APPNAME, MB_YESNO | MB_ICONQUESTION)==IDYES)
1918 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_RESOLVE)));
1919 for (INT_PTR fileindex=0; fileindex<m_targetPathList.GetCount(); ++fileindex)
1920 Resolve(m_targetPathList[fileindex], svn_wc_conflict_choose_merged, true);
1923 else
1925 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_RESOLVE)));
1926 for (INT_PTR fileindex=0; fileindex<m_targetPathList.GetCount(); ++fileindex)
1927 Resolve(m_targetPathList[fileindex], svn_wc_conflict_choose_merged, true);
1929 #endif
1930 CShellUpdater::Instance().AddPathsForUpdate(m_targetPathList);
1932 return true;
1935 bool CGitProgressList::CmdRevert(CString& sWindowTitle, bool& localoperation)
1938 localoperation = true;
1939 SetWindowTitle(IDS_PROGRS_TITLE_REVERT, g_Git.m_CurrentDir + _T("\\") + m_targetPathList.GetCommonRoot().GetUIPathString(), sWindowTitle);
1940 SetBackgroundImage(IDI_REVERT_BKG);
1942 m_itemCountTotal = 2 * m_selectedPaths.GetCount();
1943 CTGitPathList delList;
1944 for (m_itemCount = 0; m_itemCount < m_selectedPaths.GetCount(); ++m_itemCount)
1946 CTGitPath path;
1947 int action;
1948 path.SetFromWin(g_Git.m_CurrentDir + _T("\\") + m_selectedPaths[m_itemCount].GetWinPath());
1949 action = m_selectedPaths[m_itemCount].m_Action;
1950 /* rename file can't delete because it needs original file*/
1951 if((!(action & CTGitPath::LOGACTIONS_ADDED)) &&
1952 (!(action & CTGitPath::LOGACTIONS_REPLACED)))
1953 delList.AddPath(path);
1955 if (DWORD(CRegDWORD(_T("Software\\TortoiseGit\\RevertWithRecycleBin"), TRUE)))
1956 delList.DeleteAllFiles(true);
1958 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_REVERT)));
1959 for (int i = 0; i < m_selectedPaths.GetCount(); ++i)
1961 if(g_Git.Revert(_T("HEAD"), (CTGitPath&)m_selectedPaths[i]))
1963 CMessageBox::Show(NULL,_T("Revert Fail"),_T("TortoiseGit"),MB_OK|MB_ICONERROR);
1964 m_bErrorsOccurred=true;
1965 return false;
1967 Notify(m_selectedPaths[i], git_wc_notify_revert);
1968 ++m_itemCount;
1970 if (IsCancelled() == TRUE)
1972 ReportUserCanceled();
1973 return false;
1977 CShellUpdater::Instance().AddPathsForUpdate(m_selectedPaths);
1979 return true;
1982 bool CGitProgressList::CmdSwitch(CString& /*sWindowTitle*/, bool& /*localoperation*/)
1984 #if 0
1985 ASSERT(m_targetPathList.GetCount() == 1);
1986 SVNStatus st;
1987 sWindowTitle.LoadString(IDS_PROGRS_TITLE_SWITCH);
1988 SetWindowText(sWindowTitle); // needs to be updated, see TSVN rev. 21375
1989 SetBackgroundImage(IDI_SWITCH_BKG);
1990 LONG rev = 0;
1991 if (st.GetStatus(m_targetPathList[0]) != (-2))
1993 if (st.status->entry != NULL)
1995 rev = st.status->entry->revision;
1999 CString sCmdInfo;
2000 sCmdInfo.Format(IDS_PROGRS_CMD_SWITCH,
2001 m_targetPathList[0].GetWinPath(), (LPCTSTR)m_url.GetSVNPathString(),
2002 (LPCTSTR)m_Revision.ToString());
2003 ReportCmd(sCmdInfo);
2005 bool depthIsSticky = true;
2006 if (m_depth == svn_depth_unknown)
2007 depthIsSticky = false;
2008 if (!Switch(m_targetPathList[0], m_url, m_Revision, m_Revision, m_depth, depthIsSticky, m_options & ProgOptIgnoreExternals))
2010 ReportSVNError();
2011 return false;
2013 m_UpdateStartRevMap[m_targetPathList[0].GetSVNApiPath(pool)] = rev;
2014 if ((m_RevisionEnd >= 0)&&(rev >= 0)
2015 &&((LONG)m_RevisionEnd > (LONG)rev))
2017 GetDlgItem(IDC_LOGBUTTON)->ShowWindow(SW_SHOW);
2019 #endif
2020 return true;
2022 bool CGitProgressList::CmdClone(CString& sWindowTitle, bool& /*localoperation*/)
2024 if (!g_Git.UsingLibGit2(CGit::GIT_CMD_CLONE))
2026 // should never run to here
2027 ASSERT(FALSE);
2028 return false;
2030 this->m_TotalBytesTransferred = 0;
2032 SetWindowTitle(IDS_PROGRS_TITLE_CLONE, m_url.GetGitPathString(), sWindowTitle);
2033 SetBackgroundImage(IDI_SWITCH_BKG);
2034 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROG_CLONE)));
2036 if (m_url.IsEmpty() || m_targetPathList.IsEmpty())
2037 return false;
2039 CStringA url = CUnicodeUtils::GetMulti(m_url.GetGitPathString(), CP_UTF8);
2040 CStringA path = CUnicodeUtils::GetMulti(m_targetPathList[0].GetWinPathString(),CP_UTF8);
2042 git_repository *cloned_repo = NULL;
2043 git_remote *origin = NULL;
2044 git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
2046 int error = 0;
2048 checkout_opts.checkout_strategy = m_bNoCheckout? GIT_CHECKOUT_NONE : GIT_CHECKOUT_SAFE_CREATE;
2049 checkout_opts.progress_cb = CheckoutCallback;
2050 checkout_opts.progress_payload = this;
2052 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 CStringA refspecA = CUnicodeUtils::GetMulti(m_RefSpec, CP_UTF8);
2062 CStringA remoteA = CUnicodeUtils::GetMulti(m_remote, CP_UTF8);
2064 git_repository_init_options init_options = GIT_REPOSITORY_INIT_OPTIONS_INIT;
2065 init_options.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE;
2066 init_options.flags |= m_bBare ? GIT_REPOSITORY_INIT_BARE : 0;
2068 if(m_pAnimate)
2070 m_pAnimate->ShowWindow(SW_SHOW);
2071 m_pAnimate->Play(0, INT_MAX, INT_MAX);
2075 if ((error = git_repository_init_ext(&cloned_repo, path, &init_options)) < 0)
2076 break;
2077 if ((error = git_remote_create(&origin, cloned_repo, remoteA.IsEmpty() ? "origin" : remoteA, url)) < 0)
2078 break;
2079 git_remote_set_callbacks(origin, &callbacks);
2080 error = git_clone_into(cloned_repo, origin, &checkout_opts, refspecA.IsEmpty() ? nullptr : (const char*)refspecA, nullptr);
2081 } while (false);
2083 if (m_pAnimate)
2085 m_pAnimate->Stop();
2086 m_pAnimate->ShowWindow(SW_HIDE);
2089 refspecA.ReleaseBuffer();
2090 remoteA.ReleaseBuffer();
2091 git_remote_free(origin);
2092 if (error)
2094 ReportGitError();
2095 return false;
2097 else if (cloned_repo)
2098 git_repository_free(cloned_repo);
2099 return true;
2101 bool CGitProgressList::CmdSendMail(CString& sWindowTitle, bool& /*localoperation*/)
2103 ASSERT(m_SendMail);
2104 SetWindowTitle(IDS_PROGRS_TITLE_SENDMAIL, g_Git.m_CurrentDir + _T("\\") + m_targetPathList.GetCommonRoot().GetUIPathString(), sWindowTitle);
2105 //SetBackgroundImage(IDI_ADD_BKG);
2106 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_SENDMAIL)));
2108 return m_SendMail->Send(m_targetPathList, this) == 0;
2111 bool CGitProgressList::CmdFetch(CString& sWindowTitle, bool& /*localoperation*/)
2113 if (!g_Git.UsingLibGit2(CGit::GIT_CMD_FETCH))
2115 // should never run to here
2116 ASSERT(0);
2117 return false;
2119 this->m_TotalBytesTransferred = 0;
2121 SetWindowTitle(IDS_PROGRS_TITLE_FETCH, g_Git.m_CurrentDir, sWindowTitle);
2122 SetBackgroundImage(IDI_UPDATE_BKG);
2123 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_TITLE_FETCH)) + _T(" ") + m_url.GetGitPathString() + _T(" ") + m_RefSpec);
2125 CStringA url = CUnicodeUtils::GetMulti(m_url.GetGitPathString(), CP_UTF8);
2126 CStringA gitdir = CUnicodeUtils::GetMulti(CTGitPath(g_Git.m_CurrentDir).GetGitPathString(), CP_UTF8);
2127 CStringA remotebranch = CUnicodeUtils::GetMulti(m_RefSpec, CP_UTF8);
2129 git_remote *remote = NULL;
2130 git_repository *repo = NULL;
2131 bool ret = true;
2135 if (m_pAnimate)
2137 m_pAnimate->ShowWindow(SW_SHOW);
2138 m_pAnimate->Play(0, INT_MAX, INT_MAX);
2141 if (git_repository_open(&repo, gitdir))
2143 ReportGitError();
2144 ret = false;
2145 break;
2148 // first try with a named remote (e.g. "origin")
2149 if (git_remote_load(&remote, repo, url) < 0)
2151 // retry with repository located at a specific url
2152 if (git_remote_create_anonymous(&remote, repo, url, nullptr) < 0)
2154 ReportGitError();
2155 ret = false;
2156 break;
2160 git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
2162 callbacks.update_tips = RemoteUpdatetipsCallback;
2163 callbacks.sideband_progress = RemoteProgressCallback;
2164 callbacks.transfer_progress = FetchCallback;
2165 callbacks.completion = RemoteCompletionCallback;
2166 callbacks.credentials = CAppUtils::Git2GetUserPassword;
2167 callbacks.payload = this;
2169 git_remote_set_callbacks(remote, &callbacks);
2170 git_remote_set_autotag(remote, (git_remote_autotag_option_t)m_AutoTag);
2172 if (!remotebranch.IsEmpty() && git_remote_add_fetch(remote, remotebranch))
2174 ReportGitError();
2175 ret = false;
2176 break;
2179 // Connect to the remote end specifying that we want to fetch
2180 // information from it.
2181 if (git_remote_connect(remote, GIT_DIRECTION_FETCH) < 0) {
2182 ReportGitError();
2183 ret = false;
2184 break;
2187 // Download the packfile and index it. This function updates the
2188 // amount of received data and the indexer stats which lets you
2189 // inform the user about progress.
2190 if (git_remote_download(remote) < 0) {
2191 ReportGitError();
2192 ret = false;
2193 break;
2196 if (m_pAnimate)
2198 m_pAnimate->ShowWindow(SW_HIDE);
2200 // Update the references in the remote's namespace to point to the
2201 // right commits. This may be needed even if there was no packfile
2202 // to download, which can happen e.g. when the branches have been
2203 // changed but all the neede objects are available locally.
2204 if (git_remote_update_tips(remote, nullptr, nullptr) < 0)
2206 ReportGitError();
2207 ret = false;
2208 break;
2211 git_remote_disconnect(remote);
2213 } while(0);
2215 git_remote_free(remote);
2216 git_repository_free(repo);
2217 if (m_pAnimate)
2218 m_pAnimate->ShowWindow(SW_HIDE);
2219 return ret;
2222 bool CGitProgressList::CmdReset(CString& sWindowTitle, bool& /*localoperation*/)
2224 if (!g_Git.UsingLibGit2(CGit::GIT_CMD_RESET))
2226 // should never run to here
2227 ASSERT(0);
2228 return false;
2231 SetWindowTitle(IDS_PROGRS_TITLE_RESET, g_Git.m_CurrentDir, sWindowTitle);
2232 SetBackgroundImage(IDI_UPDATE_BKG);
2233 int resetTypesResource[] = { IDS_RESET_SOFT, IDS_RESET_MIXED, IDS_RESET_HARD };
2234 ReportCmd(CString(MAKEINTRESOURCE(IDS_PROGRS_TITLE_RESET)) + _T(" ") + CString(MAKEINTRESOURCE(resetTypesResource[m_resetType])) + _T(" ") + m_revision);
2236 CStringA gitdir = CUnicodeUtils::GetMulti(CTGitPath(g_Git.m_CurrentDir).GetGitPathString(), CP_UTF8);
2238 git_repository *repo = NULL;
2239 bool ret = true;
2243 if (m_pAnimate)
2245 m_pAnimate->ShowWindow(SW_SHOW);
2246 m_pAnimate->Play(0, INT_MAX, INT_MAX);
2249 if (git_repository_open(&repo, gitdir))
2251 ReportGitError();
2252 ret = false;
2253 break;
2256 CStringA revstr = CUnicodeUtils::GetUTF8(m_revision);
2257 git_object *target;
2258 git_revparse_single(&target, repo, revstr);
2259 if (git_reset(repo, target, (git_reset_t)(m_resetType + 1), nullptr, nullptr))
2261 ReportGitError();
2262 ret = false;
2263 break;
2265 } while (0);
2267 git_repository_free(repo);
2268 if (m_pAnimate)
2269 m_pAnimate->ShowWindow(SW_HIDE);
2270 return ret;
2274 void CGitProgressList::Init()
2276 SetExtendedStyle (LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER);
2278 DeleteAllItems();
2279 int c = ((CHeaderCtrl*)(GetDlgItem(0)))->GetItemCount()-1;
2280 while (c>=0)
2281 DeleteColumn(c--);
2283 CString temp;
2284 temp.LoadString(IDS_PROGRS_ACTION);
2285 InsertColumn(0, temp);
2286 temp.LoadString(IDS_PROGRS_PATH);
2287 InsertColumn(1, temp);
2289 m_pThread = AfxBeginThread(ProgressThreadEntry, this, THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED);
2290 if (m_pThread==NULL)
2292 ReportError(CString(MAKEINTRESOURCE(IDS_ERR_THREADSTARTFAILED)));
2294 else
2296 m_pThread->m_bAutoDelete = FALSE;
2297 m_pThread->ResumeThread();
2300 // Call this early so that the column headings aren't hidden before any
2301 // text gets added.
2302 ResizeColumns();
2304 SetTimer(VISIBLETIMER, 300, NULL);
2308 void CGitProgressList::OnClose()
2310 // TODO: Add your message handler code here and/or call default
2311 if (m_bCancelled)
2313 TerminateThread(m_pThread->m_hThread, (DWORD)-1);
2314 InterlockedExchange(&m_bThreadRunning, FALSE);
2316 else
2318 m_bCancelled = TRUE;
2319 return;
2321 CListCtrl::OnClose();
2325 BOOL CGitProgressList::PreTranslateMessage(MSG* pMsg)
2327 // TODO: Add your specialized code here and/or call the base class
2328 if (pMsg->message == WM_KEYDOWN)
2330 if (pMsg->wParam == 'A')
2332 if (GetKeyState(VK_CONTROL)&0x8000)
2334 // Ctrl-A -> select all
2335 SetSelectionMark(0);
2336 for (int i=0; i<GetItemCount(); ++i)
2338 SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
2342 if ((pMsg->wParam == 'C')||(pMsg->wParam == VK_INSERT))
2344 int selIndex = GetSelectionMark();
2345 if (selIndex >= 0)
2347 if (GetKeyState(VK_CONTROL)&0x8000)
2349 //Ctrl-C -> copy to clipboard
2350 CString sClipdata;
2351 POSITION pos = GetFirstSelectedItemPosition();
2352 if (pos != NULL)
2354 while (pos)
2356 int nItem = GetNextSelectedItem(pos);
2357 CString sAction = GetItemText(nItem, 0);
2358 CString sPath = GetItemText(nItem, 1);
2359 CString sMime = GetItemText(nItem, 2);
2360 CString sLogCopyText;
2361 sLogCopyText.Format(_T("%s: %s %s\r\n"),
2362 (LPCTSTR)sAction, (LPCTSTR)sPath, (LPCTSTR)sMime);
2363 sClipdata += sLogCopyText;
2365 CStringUtils::WriteAsciiStringToClipboard(sClipdata);
2370 } // if (pMsg->message == WM_KEYDOWN)
2371 return CListCtrl::PreTranslateMessage(pMsg);
2374 CString CGitProgressList::GetPathFromColumnText(const CString& sColumnText)
2376 CString sPath = sColumnText;
2377 if (sPath.Find(':')<0)
2379 // the path is not absolute: add the common root of all paths to it
2380 sPath = g_Git.m_CurrentDir + _T("\\") + sColumnText;
2382 return sPath;
2385 void CGitProgressList::SetWindowTitle(UINT id, const CString& urlorpath, CString& dialogname)
2387 if (!m_bSetTitle || !m_pPostWnd)
2388 return;
2390 dialogname.LoadString(id);
2391 CAppUtils::SetWindowTitle(m_pPostWnd->GetSafeHwnd(), urlorpath, dialogname);