11#include "builtin.h"
22#include "cache.h"
33#include "commit.h"
4+ #include "refs.h"
5+ #include "diff.h"
6+ #include "revision.h"
47#include "parse-options.h"
58
69static int show_merge_base (struct commit * * rev , int rev_nr , int show_all )
@@ -27,6 +30,7 @@ static const char * const merge_base_usage[] = {
2730 N_ ("git merge-base [-a|--all] --octopus <commit>..." ),
2831 N_ ("git merge-base --independent <commit>..." ),
2932 N_ ("git merge-base --is-ancestor <commit> <commit>" ),
33+ N_ ("git merge-base --fork-point <ref> [<commit>]" ),
3034 NULL
3135};
3236
@@ -85,37 +89,148 @@ static int handle_is_ancestor(int argc, const char **argv)
8589 return 1 ;
8690}
8791
92+ struct rev_collect {
93+ struct commit * * commit ;
94+ int nr ;
95+ int alloc ;
96+ unsigned int initial : 1 ;
97+ };
98+
99+ static void add_one_commit (unsigned char * sha1 , struct rev_collect * revs )
100+ {
101+ struct commit * commit ;
102+
103+ if (is_null_sha1 (sha1 ))
104+ return ;
105+
106+ commit = lookup_commit (sha1 );
107+ if (!commit ||
108+ (commit -> object .flags & TMP_MARK ) ||
109+ parse_commit (commit ))
110+ return ;
111+
112+ ALLOC_GROW (revs -> commit , revs -> nr + 1 , revs -> alloc );
113+ revs -> commit [revs -> nr ++ ] = commit ;
114+ commit -> object .flags |= TMP_MARK ;
115+ }
116+
117+ static int collect_one_reflog_ent (unsigned char * osha1 , unsigned char * nsha1 ,
118+ const char * ident , unsigned long timestamp ,
119+ int tz , const char * message , void * cbdata )
120+ {
121+ struct rev_collect * revs = cbdata ;
122+
123+ if (revs -> initial ) {
124+ revs -> initial = 0 ;
125+ add_one_commit (osha1 , revs );
126+ }
127+ add_one_commit (nsha1 , revs );
128+ return 0 ;
129+ }
130+
131+ static int handle_fork_point (int argc , const char * * argv )
132+ {
133+ unsigned char sha1 [20 ];
134+ char * refname ;
135+ const char * commitname ;
136+ struct rev_collect revs ;
137+ struct commit * derived ;
138+ struct commit_list * bases ;
139+ int i , ret = 0 ;
140+
141+ switch (dwim_ref (argv [0 ], strlen (argv [0 ]), sha1 , & refname )) {
142+ case 0 :
143+ die ("No such ref: '%s'" , argv [0 ]);
144+ case 1 :
145+ break ; /* good */
146+ default :
147+ die ("Ambiguous refname: '%s'" , argv [0 ]);
148+ }
149+
150+ commitname = (argc == 2 ) ? argv [1 ] : "HEAD" ;
151+ if (get_sha1 (commitname , sha1 ))
152+ die ("Not a valid object name: '%s'" , commitname );
153+
154+ derived = lookup_commit_reference (sha1 );
155+ memset (& revs , 0 , sizeof (revs ));
156+ revs .initial = 1 ;
157+ for_each_reflog_ent (refname , collect_one_reflog_ent , & revs );
158+
159+ for (i = 0 ; i < revs .nr ; i ++ )
160+ revs .commit [i ]-> object .flags &= ~TMP_MARK ;
161+
162+ bases = get_merge_bases_many (derived , revs .nr , revs .commit , 0 );
163+
164+ /*
165+ * There should be one and only one merge base, when we found
166+ * a common ancestor among reflog entries.
167+ */
168+ if (!bases || bases -> next ) {
169+ ret = 1 ;
170+ goto cleanup_return ;
171+ }
172+
173+ /* And the found one must be one of the reflog entries */
174+ for (i = 0 ; i < revs .nr ; i ++ )
175+ if (& bases -> item -> object == & revs .commit [i ]-> object )
176+ break ; /* found */
177+ if (revs .nr <= i ) {
178+ ret = 1 ; /* not found */
179+ goto cleanup_return ;
180+ }
181+
182+ printf ("%s\n" , sha1_to_hex (bases -> item -> object .sha1 ));
183+
184+ cleanup_return :
185+ free_commit_list (bases );
186+ return ret ;
187+ }
188+
88189int cmd_merge_base (int argc , const char * * argv , const char * prefix )
89190{
90191 struct commit * * rev ;
91192 int rev_nr = 0 ;
92193 int show_all = 0 ;
93- int octopus = 0 ;
94- int reduce = 0 ;
95- int is_ancestor = 0 ;
194+ int cmdmode = 0 ;
96195
97196 struct option options [] = {
98197 OPT_BOOL ('a' , "all" , & show_all , N_ ("output all common ancestors" )),
99- OPT_BOOL (0 , "octopus" , & octopus , N_ ("find ancestors for a single n-way merge" )),
100- OPT_BOOL (0 , "independent" , & reduce , N_ ("list revs not reachable from others" )),
101- OPT_BOOL (0 , "is-ancestor" , & is_ancestor ,
102- N_ ("is the first one ancestor of the other?" )),
198+ OPT_CMDMODE (0 , "octopus" , & cmdmode ,
199+ N_ ("find ancestors for a single n-way merge" ), 'o' ),
200+ OPT_CMDMODE (0 , "independent" , & cmdmode ,
201+ N_ ("list revs not reachable from others" ), 'r' ),
202+ OPT_CMDMODE (0 , "is-ancestor" , & cmdmode ,
203+ N_ ("is the first one ancestor of the other?" ), 'a' ),
204+ OPT_CMDMODE (0 , "fork-point" , & cmdmode ,
205+ N_ ("find where <commit> forked from reflog of <ref>" ), 'f' ),
103206 OPT_END ()
104207 };
105208
106209 git_config (git_default_config , NULL );
107210 argc = parse_options (argc , argv , prefix , options , merge_base_usage , 0 );
108- if (!octopus && !reduce && argc < 2 )
109- usage_with_options (merge_base_usage , options );
110- if (is_ancestor && (show_all || octopus || reduce ))
111- die ("--is-ancestor cannot be used with other options" );
112- if (is_ancestor )
211+
212+ if (cmdmode == 'a' ) {
213+ if (argc < 2 )
214+ usage_with_options (merge_base_usage , options );
215+ if (show_all )
216+ die ("--is-ancestor cannot be used with --all" );
113217 return handle_is_ancestor (argc , argv );
114- if (reduce && (show_all || octopus ))
115- die ("--independent cannot be used with other options" );
218+ }
219+
220+ if (cmdmode == 'r' && show_all )
221+ die ("--independent cannot be used with --all" );
116222
117- if (octopus || reduce )
118- return handle_octopus (argc , argv , reduce , show_all );
223+ if (cmdmode == 'r' || cmdmode == 'o' )
224+ return handle_octopus (argc , argv , cmdmode == 'r' , show_all );
225+
226+ if (cmdmode == 'f' ) {
227+ if (argc < 1 || 2 < argc )
228+ usage_with_options (merge_base_usage , options );
229+ return handle_fork_point (argc , argv );
230+ }
231+
232+ if (argc < 2 )
233+ usage_with_options (merge_base_usage , options );
119234
120235 rev = xmalloc (argc * sizeof (* rev ));
121236 while (argc -- > 0 )
0 commit comments