Coverage for src / gitq / git_split.py: 91%

53 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-15 15:32 -0400

1#!/usr/bin/env python3 

2 

3import sys 

4import argparse 

5from contextlib import contextmanager 

6from typing import Iterator 

7from dataclasses import dataclass, field 

8 

9from . import continuations 

10from .continuations import Continuation, EditBranch, Suspend, Abort 

11from .git_swap import edit_commit, PickCherryWithReference 

12 

13description = """ 

14Splits a single commit into two or more commits. The user can add a 

15additional commits, while holding the final content constant. 

16 

17Checks out COMMIT with its changes staged, via `git reset --soft HEAD^`, 

18suspending so the user can make one or more new commits. After the user 

19continues, COMMIT will be restored with its original content, and git-split 

20will suspend again so the user can amend the commit message with 

21`git commit --amend`. 

22 

23After continuing again, it replays the remaining commits from above COMMIT 

24on top. 

25""" 

26 

27 

28@dataclass 

29class SuspendForAmend(Continuation): 

30 """Suspend after the inner block completes, so the user can run 

31 `git commit --amend` before continuing.""" 

32 

33 target: str 

34 done: bool = field(default=False) 

35 

36 @contextmanager 

37 def impl(self) -> Iterator[None]: 

38 yield 

39 if not self.done: 

40 self.done = True 

41 raise Suspend( 

42 status=f"Commit {self.target} was split.\n" 

43 + "Edit the message with: git commit --amend\n" 

44 + "Then resume with: git split --continue." 

45 ) 

46 

47 

48class Main(continuations.Main): 

49 

50 tool = "git-split" 

51 suspend_message = "Resume with `git split --continue` when done." 

52 

53 def main(self): 

54 parser = argparse.ArgumentParser( 

55 description=description, formatter_class=argparse.RawDescriptionHelpFormatter 

56 ) 

57 parser.add_argument("commit", nargs="?", metavar="COMMIT") 

58 parser.add_argument( 

59 "--continue", 

60 "-c", 

61 action="store_true", 

62 dest="resume", 

63 ) 

64 parser.add_argument( 

65 "--abort", 

66 action="store_true", 

67 ) 

68 parser.add_argument("--status", action="store_true", help="print status") 

69 args = parser.parse_args() 

70 

71 if args.resume: 

72 self.resume(None) 

73 return 

74 

75 if args.abort: 

76 self.resume(Abort()) 

77 

78 if args.status: 

79 self.status() 

80 return 

81 

82 if not args.commit: 82 ↛ 83line 82 didn't jump to line 83 because the condition on line 82 was never true

83 parser.print_usage() 

84 sys.exit(1) 

85 

86 with self.setup(): 

87 commit = self.git.commit(args.commit) 

88 sha = commit.sha 

89 target = self.git.abbrev(sha) 

90 status = ( 

91 f"Splitting {target}.\n" 

92 + "Changes from the commit are now staged.\n" 

93 + "Make one or more commits here, then resume with: git split --continue." 

94 ) 

95 

96 with EditBranch(message="git-split"): 

97 with edit_commit(commit, git=self.git, edit=True): 

98 with SuspendForAmend(target): 

99 with PickCherryWithReference(cherry=sha, reference=sha): 

100 self.git("reset", "--soft", "HEAD^") 

101 raise Suspend(status=status) 

102 

103 

104main = Main() 

105 

106if __name__ == "__main__": 106 ↛ 107line 106 didn't jump to line 107 because the condition on line 106 was never true

107 main()