55# import datetime
66
77# from turtle import write
8- from typing import List , Coroutine
8+ from typing import List , Coroutine , Optional
99
1010# from unittest import result
1111
@@ -49,6 +49,70 @@ async def _run_command(args: List[str], safe: bool = False) -> str:
4949 return stdout .decode ().strip ()
5050
5151
52+ async def get_local_only_commits (
53+ repository : Repository , claims : Optional [List [str ]] = None
54+ ) -> list :
55+ """
56+ Returns:
57+ list:
58+ Commits that are not on remote branches. Includes a commit that
59+ represents uncommitted changes.
60+ claims (list, optional):
61+ List of files that we want to claim.
62+ They'll be store as uncommitted changes.
63+ """
64+ local_commits = []
65+ for branch in repository .branches :
66+ await accumulate_local_only_commits (repository , branch .commit , local_commits )
67+ if repository .config .get ("track_uncommitted" ):
68+ uncommitted_changes_commit = repository .get_uncommitted_changes_commit (
69+ claims = claims
70+ )
71+
72+ # Adding file we want to claim to the uncommitted changes commit.
73+ for claim in claims or []:
74+ claim = repository .get_absolute_path (claim )
75+ if os .path .isfile (claim ):
76+ uncommitted_changes_commit .setdefault ("changes" , []).append (
77+ repository .get_relative_path (claim ).replace ("\\ " , "/" )
78+ )
79+
80+ if uncommitted_changes_commit :
81+ local_commits .insert (0 , uncommitted_changes_commit )
82+ local_commits .sort (key = lambda commit : commit .get ("date" ), reverse = True )
83+ return local_commits
84+
85+
86+ async def accumulate_local_only_commits (
87+ repository , start : git .Commit , local_commits : List [dict ]
88+ ):
89+ """Accumulates a list of local only commit starting from the provided commit.
90+
91+ Args:
92+ local_commits (list): The accumulated local commits.
93+ start (git.objects.Commit):
94+ The commit that we start peeling from last commit.
95+ """
96+ if repository .is_remote_commit (start .hexsha ):
97+ return
98+
99+ commit = Commit (repository )
100+ commit .update_with_sha (start .hexsha )
101+ commit .update_context ()
102+
103+ changes = await get_commits_changes ([commit ])
104+ commit ["changes" ] = changes [0 ]
105+
106+ branches_list = await get_commits_branches ([commit ])
107+ branches = branches_list [0 ] if branches_list else []
108+ commit ["branches" ] = {"local" : branches }
109+
110+ if commit not in local_commits :
111+ local_commits .append (commit )
112+ for parent in start .parents :
113+ await accumulate_local_only_commits (repository , parent , local_commits )
114+
115+
52116async def get_files_last_commits ( # pylint: disable=too-many-branches,too-many-locals,too-many-statements
53117 filenames : List [str ], prune : bool = True
54118) -> List [Commit ]:
@@ -271,21 +335,14 @@ async def update_files_permissions(filenames: List[str]):
271335 write_permissions = []
272336 for filename , last_commit in zip (filenames , last_commits ):
273337 repository = Repository .from_filename (os .path .dirname (filename ))
274- spread = last_commit .commit_spread if repository else 0
338+ if not repository :
339+ continue
340+ spread = last_commit .commit_spread
275341 if not spread :
276342 continue
277- is_uncommitted = (
278- spread & CommitSpread .MINE_UNCOMMITTED == CommitSpread .MINE_UNCOMMITTED
279- )
280- is_local = (
281- spread & CommitSpread .MINE_ACTIVE_BRANCH == CommitSpread .MINE_ACTIVE_BRANCH
282- )
283- is_current = (
284- spread
285- & (CommitSpread .MINE_ACTIVE_BRANCH | CommitSpread .REMOTE_MATCHING_BRANCH )
286- == CommitSpread .MINE_ACTIVE_BRANCH | CommitSpread .REMOTE_MATCHING_BRANCH
287- )
288- write_permission = is_uncommitted or is_local or is_current
343+ is_uncommitted = spread == CommitSpread .MINE_UNCOMMITTED
344+ is_local = spread == CommitSpread .MINE_ACTIVE_BRANCH
345+ write_permission = is_uncommitted or is_local
289346 write_permissions .append (write_permission )
290347 tasks .append (_set_write_permission (filename , write_permission ))
291348 await asyncio .gather (* tasks )
@@ -294,8 +351,7 @@ async def update_files_permissions(filenames: List[str]):
294351async def _set_write_permission (
295352 filename : str , write_permission : bool , safe : bool = False
296353) -> bool :
297- """
298- Set the write permission of a file asynchronously.
354+ """Set the write permission of a file asynchronously.
299355
300356 Args:
301357 filename (str): The path to the file.
@@ -324,3 +380,87 @@ async def _set_write_permission(
324380 return True
325381 raise
326382 return True
383+
384+
385+ async def get_updated_tracked_commits (
386+ repository : Repository , claims : Optional [List [str ]] = None
387+ ) -> list :
388+ """
389+ Returns:
390+ list:
391+ Local commits for all clones with local commits and uncommitted changes
392+ from this clone.
393+ """
394+ tracked_commits = []
395+ for commit in repository .store .commits :
396+ remote = repository .remote .url
397+ is_other_remote = commit .get ("remote" ) != remote
398+ if is_other_remote or not commit .is_issued_commit ():
399+ tracked_commits .append (commit )
400+ continue
401+
402+ for commit in await get_local_only_commits (repository , claims = claims ):
403+ tracked_commits .append (commit )
404+ return tracked_commits
405+
406+
407+ async def update_tracked_commits (
408+ repository : Repository , claims : Optional [List [str ]] = None
409+ ):
410+ """Pulls the tracked commits from the store and updates them."""
411+ repository .store .commits = await get_updated_tracked_commits (
412+ repository , claims = claims
413+ )
414+ absolute_filenames = []
415+ if repository .config .get ("modify_permissions" ):
416+ print ("Updating permissions" )
417+ for filename in repository .files :
418+ absolute_filenames .append (repository .get_absolute_path (filename ))
419+ await repository .batch .update_files_permissions (absolute_filenames )
420+
421+
422+ async def claim_files (
423+ filenames : List [str ],
424+ prune : bool = True ,
425+ ) -> List [Commit ]:
426+ """If the file is available for changes, temporarily communicates files as changed.
427+ By communicate we mean the file will be marked as a local change until the next
428+ update of the tracked commits. Also makes the files writable if the configured is
429+ set to affect permissions.
430+
431+ Args:
432+ filename (str):
433+ A list of absolute filenames to claim.
434+ prune (bool, optional): Prune branches if a fetch is necessary.
435+
436+ Returns:
437+ List[Commit]: The blocking commits for the file we want to claim.
438+ """
439+ blocking_commits = []
440+ last_commits = await get_files_last_commits (filenames , prune = prune )
441+ for last_commit in last_commits :
442+ spread = last_commit .commit_spread
443+ is_local_commit = (
444+ spread & CommitSpread .MINE_ACTIVE_BRANCH == CommitSpread .MINE_ACTIVE_BRANCH
445+ )
446+ is_uncommitted = (
447+ spread & CommitSpread .MINE_UNCOMMITTED == CommitSpread .MINE_UNCOMMITTED
448+ )
449+ blocking_commits .append (
450+ Commit (None ) if is_local_commit or is_uncommitted else last_commit
451+ )
452+
453+ # Collect all repositories and corresponding file updates.
454+ filenames_by_repository = {}
455+ for filename_ in filenames :
456+ repository = Repository .from_filename (filename_ )
457+ filenames_by_repository .setdefault (repository , []).append (filename_ )
458+
459+ # Updating the tracked commits for each repository affected.
460+ for repository in filenames_by_repository :
461+ if repository :
462+ await update_tracked_commits (
463+ repository ,
464+ claims = filenames_by_repository .get (repository , []),
465+ )
466+ return blocking_commits
0 commit comments