@@ -64,6 +64,12 @@ enum display_format {
6464 DISPLAY_FORMAT_PORCELAIN ,
6565};
6666
67+ enum prune_local_mode {
68+ PRUNE_LOCAL_OFF = 0 ,
69+ PRUNE_LOCAL_SAFE ,
70+ PRUNE_LOCAL_FORCE ,
71+ };
72+
6773struct display_state {
6874 struct strbuf buf ;
6975
@@ -105,12 +111,31 @@ struct fetch_config {
105111 int all ;
106112 int prune ;
107113 int prune_tags ;
114+ enum prune_local_mode prune_local ;
108115 int show_forced_updates ;
109116 int recurse_submodules ;
110117 int parallel ;
111118 int submodule_fetch_jobs ;
112119};
113120
121+ static enum prune_local_mode parse_prune_local (const char * k , const char * v )
122+ {
123+ if (v ) {
124+ if (!strcasecmp (v , "safe" ))
125+ return PRUNE_LOCAL_SAFE ;
126+ if (!strcasecmp (v , "force" ) || !strcasecmp (v , "unsafe" ))
127+ return PRUNE_LOCAL_FORCE ;
128+ }
129+ switch (git_parse_maybe_bool (v )) {
130+ case 1 :
131+ return PRUNE_LOCAL_SAFE ;
132+ case 0 :
133+ return PRUNE_LOCAL_OFF ;
134+ default :
135+ die (_ ("invalid value for '%s': '%s'" ), k , v );
136+ }
137+ }
138+
114139static int git_fetch_config (const char * k , const char * v ,
115140 const struct config_context * ctx , void * cb )
116141{
@@ -131,6 +156,11 @@ static int git_fetch_config(const char *k, const char *v,
131156 return 0 ;
132157 }
133158
159+ if (!strcmp (k , "fetch.prunelocalbranches" )) {
160+ fetch_config -> prune_local = parse_prune_local (k , v );
161+ return 0 ;
162+ }
163+
134164 if (!strcmp (k , "fetch.showforcedupdates" )) {
135165 fetch_config -> show_forced_updates = git_config_bool (k , v );
136166 return 0 ;
@@ -1445,7 +1475,8 @@ static int fetch_and_consume_refs(struct display_state *display_state,
14451475static int prune_refs (struct display_state * display_state ,
14461476 struct refspec * rs ,
14471477 struct ref_transaction * transaction ,
1448- struct ref * ref_map )
1478+ struct ref * ref_map ,
1479+ struct ref * * stale_refs_out )
14491480{
14501481 int result = 0 ;
14511482 struct ref * ref , * stale_refs = get_stale_heads (rs , ref_map );
@@ -1487,7 +1518,140 @@ static int prune_refs(struct display_state *display_state,
14871518cleanup :
14881519 string_list_clear (& refnames , 0 );
14891520 strbuf_release (& err );
1490- free_refs (stale_refs );
1521+ if (!result && stale_refs_out )
1522+ * stale_refs_out = stale_refs ;
1523+ else
1524+ free_refs (stale_refs );
1525+ return result ;
1526+ }
1527+
1528+ struct prune_local_cb {
1529+ struct string_list * pruned_refs ;
1530+ struct string_list * to_delete ;
1531+ struct string_list * skipped_unmerged ;
1532+ enum prune_local_mode mode ;
1533+ };
1534+
1535+ static int collect_local_to_prune (const struct reference * ref , void * cb_data )
1536+ {
1537+ struct prune_local_cb * cb = cb_data ;
1538+ const char * short_name = ref -> name ;
1539+ struct strbuf full_ref = STRBUF_INIT ;
1540+ struct branch * branch ;
1541+ const char * upstream ;
1542+ struct string_list_item * pruned ;
1543+ int result = 0 ;
1544+
1545+ if (ref -> flags & REF_ISSYMREF )
1546+ return 0 ;
1547+
1548+ strbuf_addf (& full_ref , "refs/heads/%s" , short_name );
1549+ if (branch_checked_out (full_ref .buf ))
1550+ goto cleanup ;
1551+
1552+ branch = branch_get (short_name );
1553+ upstream = branch_get_upstream (branch , NULL );
1554+ if (!upstream )
1555+ goto cleanup ;
1556+
1557+ pruned = string_list_lookup (cb -> pruned_refs , upstream );
1558+ if (!pruned )
1559+ goto cleanup ;
1560+
1561+ if (cb -> mode == PRUNE_LOCAL_SAFE ) {
1562+ struct commit * local_commit , * upstream_commit ;
1563+ const struct object_id * upstream_oid = pruned -> util ;
1564+ int reachable ;
1565+
1566+ local_commit = lookup_commit_reference (the_repository , ref -> oid );
1567+ if (!local_commit )
1568+ goto cleanup ;
1569+
1570+ upstream_commit = lookup_commit_reference (the_repository ,
1571+ upstream_oid );
1572+ if (!upstream_commit ) {
1573+ string_list_append (cb -> skipped_unmerged , short_name );
1574+ goto cleanup ;
1575+ }
1576+
1577+ reachable = repo_in_merge_bases (the_repository , local_commit ,
1578+ upstream_commit );
1579+ if (reachable < 0 ) {
1580+ result = -1 ;
1581+ goto cleanup ;
1582+ }
1583+ if (!reachable ) {
1584+ string_list_append (cb -> skipped_unmerged , short_name );
1585+ goto cleanup ;
1586+ }
1587+ }
1588+
1589+ string_list_append (cb -> to_delete , full_ref .buf );
1590+
1591+ cleanup :
1592+ strbuf_release (& full_ref );
1593+ return result ;
1594+ }
1595+
1596+ static int prune_local_branches (struct display_state * display_state ,
1597+ struct ref * stale_refs ,
1598+ enum prune_local_mode mode )
1599+ {
1600+ struct string_list pruned_refs = STRING_LIST_INIT_NODUP ;
1601+ struct string_list to_delete = STRING_LIST_INIT_DUP ;
1602+ struct string_list skipped_unmerged = STRING_LIST_INIT_DUP ;
1603+ struct prune_local_cb cb = {
1604+ .pruned_refs = & pruned_refs ,
1605+ .to_delete = & to_delete ,
1606+ .skipped_unmerged = & skipped_unmerged ,
1607+ .mode = mode ,
1608+ };
1609+ struct ref * ref ;
1610+ struct string_list_item * item ;
1611+ int result = 0 ;
1612+
1613+ if (!stale_refs )
1614+ return 0 ;
1615+
1616+ for (ref = stale_refs ; ref ; ref = ref -> next )
1617+ string_list_append (& pruned_refs , ref -> name )-> util = & ref -> new_oid ;
1618+ string_list_sort (& pruned_refs );
1619+
1620+ if (refs_for_each_branch_ref (get_main_ref_store (the_repository ),
1621+ collect_local_to_prune , & cb )) {
1622+ result = -1 ;
1623+ goto cleanup ;
1624+ }
1625+
1626+ if (!dry_run && to_delete .nr )
1627+ result = refs_delete_refs (get_main_ref_store (the_repository ),
1628+ "fetch: prune local branches" ,
1629+ & to_delete , REF_NO_DEREF );
1630+
1631+ if (verbosity >= 0 ) {
1632+ const struct object_id * zero = null_oid (the_repository -> hash_algo );
1633+ for_each_string_list_item (item , & to_delete ) {
1634+ const char * short_name ;
1635+ if (skip_prefix (item -> string , "refs/heads/" , & short_name ))
1636+ display_ref_update (display_state , '-' ,
1637+ _ ("[deleted local]" ), NULL ,
1638+ _ ("(none)" ), short_name ,
1639+ zero , zero ,
1640+ transport_summary_width (NULL ));
1641+ }
1642+ for_each_string_list_item (item , & skipped_unmerged )
1643+ warning (_ ("not deleting local branch '%s' that is not "
1644+ "fully merged into its upstream;\n"
1645+ " set fetch.pruneLocalBranches=force to "
1646+ "delete anyway, or delete manually with "
1647+ "'git branch -D %s'" ),
1648+ item -> string , item -> string );
1649+ }
1650+
1651+ cleanup :
1652+ string_list_clear (& pruned_refs , 0 );
1653+ string_list_clear (& to_delete , 0 );
1654+ string_list_clear (& skipped_unmerged , 0 );
14911655 return result ;
14921656}
14931657
@@ -1945,19 +2109,28 @@ static int do_fetch(struct transport *transport,
19452109 if (tags == TAGS_DEFAULT && autotags )
19462110 transport_set_option (transport , TRANS_OPT_FOLLOWTAGS , "1" );
19472111 if (prune ) {
2112+ struct ref * stale_refs = NULL ;
2113+ struct ref * * stale_refs_out = config -> prune_local != PRUNE_LOCAL_OFF
2114+ ? & stale_refs : NULL ;
19482115 /*
19492116 * We only prune based on refspecs specified
19502117 * explicitly (via command line or configuration); we
19512118 * don't care whether --tags was specified.
19522119 */
19532120 if (rs -> nr ) {
1954- retcode = prune_refs (& display_state , rs , transaction , ref_map );
2121+ retcode = prune_refs (& display_state , rs , transaction ,
2122+ ref_map , stale_refs_out );
19552123 } else {
19562124 retcode = prune_refs (& display_state , & transport -> remote -> fetch ,
1957- transaction , ref_map );
2125+ transaction , ref_map , stale_refs_out );
19582126 }
19592127 if (retcode != 0 )
19602128 retcode = 1 ;
2129+ else if (stale_refs &&
2130+ prune_local_branches (& display_state , stale_refs ,
2131+ config -> prune_local ))
2132+ retcode = 1 ;
2133+ free_refs (stale_refs );
19612134 }
19622135
19632136 /*
@@ -2469,6 +2642,7 @@ int cmd_fetch(int argc,
24692642 .display_format = DISPLAY_FORMAT_FULL ,
24702643 .prune = -1 ,
24712644 .prune_tags = -1 ,
2645+ .prune_local = PRUNE_LOCAL_OFF ,
24722646 .show_forced_updates = 1 ,
24732647 .recurse_submodules = RECURSE_SUBMODULES_DEFAULT ,
24742648 .parallel = 1 ,
0 commit comments