@@ -18,9 +18,11 @@ use std::process::Command;
18
18
19
19
use console:: style;
20
20
use dialoguer:: { Confirmation , Select } ;
21
- use git2:: { Branch , Commit , Diff , Repository } ;
21
+ use git2:: { Branch , Commit , Diff , Object , ObjectType , Oid , Repository } ;
22
22
use structopt:: StructOpt ;
23
23
24
+ const UPSTREAM_VAR : & str = "GIT_INSTAFIX_UPSTREAM" ;
25
+
24
26
#[ derive( StructOpt , Debug ) ]
25
27
#[ structopt(
26
28
about = "Fix a commit in your history with your currently-staged changes" ,
@@ -69,34 +71,71 @@ fn main() {
69
71
70
72
fn run ( squash : bool , max_commits : usize ) -> Result < ( ) , Box < dyn Error > > {
71
73
let repo = Repository :: open ( "." ) ?;
72
- match repo. head ( ) {
73
- Ok ( head) => {
74
- let head_tree = head. peel_to_tree ( ) ?;
75
- let head_branch = Branch :: wrap ( head) ;
76
- let diff = repo. diff_tree_to_index ( Some ( & head_tree) , None , None ) ?;
77
- let commit_to_amend =
78
- create_fixup_commit ( & repo, & head_branch, & diff, squash, max_commits) ?;
79
- println ! (
80
- "selected: {} {}" ,
81
- & commit_to_amend. id( ) . to_string( ) [ 0 ..10 ] ,
82
- commit_to_amend. summary( ) . unwrap_or( "" )
83
- ) ;
84
- // do the rebase
85
- let target_id = format ! ( "{}~" , commit_to_amend. id( ) ) ;
86
- Command :: new ( "git" )
87
- . args ( & [ "rebase" , "--interactive" , "--autosquash" , & target_id] )
88
- . env ( "GIT_SEQUENCE_EDITOR" , "true" )
89
- . spawn ( ) ?
90
- . wait ( ) ?;
91
- }
92
- Err ( e) => return Err ( format ! ( "head is not pointing at a valid branch: {}" , e) . into ( ) ) ,
93
- } ;
74
+ let head = repo
75
+ . head ( )
76
+ . map_err ( |e| format ! ( "HEAD is not pointing at a valid branch: {}" , e) ) ?;
77
+ let head_tree = head. peel_to_tree ( ) ?;
78
+ let head_branch = Branch :: wrap ( head) ;
79
+ let diff = repo. diff_tree_to_index ( Some ( & head_tree) , None , None ) ?;
80
+ let upstream = get_upstream ( & repo, & head_branch) ?;
81
+ let commit_to_amend =
82
+ create_fixup_commit ( & repo, & head_branch, & upstream, & diff, squash, max_commits) ?;
83
+ println ! (
84
+ "selected: {} {}" ,
85
+ & commit_to_amend. id( ) . to_string( ) [ 0 ..10 ] ,
86
+ commit_to_amend. summary( ) . unwrap_or( "" )
87
+ ) ;
88
+ // do the rebase
89
+ let target_id = format ! ( "{}~" , commit_to_amend. id( ) ) ;
90
+ Command :: new ( "git" )
91
+ . args ( & [ "rebase" , "--interactive" , "--autosquash" , & target_id] )
92
+ . env ( "GIT_SEQUENCE_EDITOR" , "true" )
93
+ . spawn ( ) ?
94
+ . wait ( ) ?;
94
95
Ok ( ( ) )
95
96
}
96
97
98
+ fn get_upstream < ' a > (
99
+ repo : & ' a Repository ,
100
+ head_branch : & ' a Branch ,
101
+ ) -> Result < Object < ' a > , Box < dyn Error > > {
102
+ let upstream = if let Ok ( upstream_name) = env:: var ( UPSTREAM_VAR ) {
103
+ let branch = repo
104
+ . branches ( None ) ?
105
+ . filter_map ( |branch| branch. ok ( ) . map ( |( b, _type) | b) )
106
+ . find ( |b| {
107
+ b. name ( )
108
+ . map ( |n| n. expect ( "valid utf8 branchname" ) == & upstream_name)
109
+ . unwrap_or ( false )
110
+ } )
111
+ . ok_or_else ( || format ! ( "cannot find branch with name {:?}" , upstream_name) ) ?;
112
+ let result = Command :: new ( "git" )
113
+ . args ( & [
114
+ "merge-base" ,
115
+ head_branch. name ( ) . unwrap ( ) . unwrap ( ) ,
116
+ branch. name ( ) . unwrap ( ) . unwrap ( ) ,
117
+ ] )
118
+ . output ( ) ?
119
+ . stdout ;
120
+ let oid = Oid :: from_str ( std:: str:: from_utf8 ( & result) ?. trim ( ) ) ?;
121
+ let commit = repo. find_object ( oid, None ) . unwrap ( ) ;
122
+
123
+ commit
124
+ } else {
125
+ head_branch
126
+ . upstream ( )
127
+ . unwrap ( )
128
+ . into_reference ( )
129
+ . peel ( ObjectType :: Commit ) ?
130
+ } ;
131
+
132
+ Ok ( upstream)
133
+ }
134
+
97
135
fn create_fixup_commit < ' a > (
98
136
repo : & ' a Repository ,
99
137
head_branch : & ' a Branch ,
138
+ upstream : & ' a Object ,
100
139
diff : & ' a Diff ,
101
140
squash : bool ,
102
141
max_commits : usize ,
@@ -120,7 +159,7 @@ fn create_fixup_commit<'a>(
120
159
println ! ( "Staged changes:" ) ;
121
160
print_diff ( Changes :: Staged ) ?;
122
161
}
123
- let commit_to_amend = select_commit_to_amend ( & repo, head_branch . upstream ( ) . ok ( ) , max_commits) ?;
162
+ let commit_to_amend = select_commit_to_amend ( & repo, Some ( upstream ) , max_commits) ?;
124
163
do_fixup_commit ( & repo, & head_branch, & commit_to_amend, squash) ?;
125
164
Ok ( commit_to_amend)
126
165
}
@@ -147,13 +186,13 @@ fn do_fixup_commit<'a>(
147
186
148
187
fn select_commit_to_amend < ' a > (
149
188
repo : & ' a Repository ,
150
- upstream : Option < Branch < ' a > > ,
189
+ upstream : Option < & Object < ' a > > ,
151
190
max_commits : usize ,
152
191
) -> Result < Commit < ' a > , Box < dyn Error > > {
153
192
let mut walker = repo. revwalk ( ) ?;
154
193
walker. push_head ( ) ?;
155
194
let commits = if let Some ( upstream) = upstream {
156
- let upstream_oid = upstream. get ( ) . target ( ) . expect ( "No upstream target" ) ;
195
+ let upstream_oid = upstream. id ( ) ;
157
196
walker
158
197
. flat_map ( |r| r)
159
198
. take_while ( |rev| * rev != upstream_oid)
0 commit comments