11#include " ../subcommand/push_subcommand.hpp"
22
33#include < iostream>
4+ #include < unordered_map>
45
56#include < git2/remote.h>
67
8+ #include " ../utils/ansi_code.hpp"
9+ #include " ../utils/common.hpp"
710#include " ../utils/credentials.hpp"
811#include " ../utils/progress.hpp"
912#include " ../wasm/scope.hpp"
@@ -14,8 +17,14 @@ push_subcommand::push_subcommand(const libgit2_object&, CLI::App& app)
1417 auto * sub = app.add_subcommand (" push" , " Update remote refs along with associated objects" );
1518
1619 sub->add_option (" <remote>" , m_remote_name, " The remote to push to" )->default_val (" origin" );
17-
18- sub->add_option (" <refspec>" , m_refspecs, " The refspec(s) to push" );
20+ sub->add_option (" <refspec>" , m_refspecs, " The refspec(s) to push" )
21+ ->expected (0 ,-1 );
22+ sub->add_flag (
23+ " --all,--branches" ,
24+ m_branches_flag,
25+ " Push all branches (i.e. refs under " + ansi_code::bold + " refs/heads/" + ansi_code::reset
26+ + " ); cannot be used with other <refspec>."
27+ );
1928
2029 sub->callback (
2130 [this ]()
@@ -40,25 +49,132 @@ void push_subcommand::run()
4049 push_opts.callbacks .push_transfer_progress = push_transfer_progress;
4150 push_opts.callbacks .push_update_reference = push_update_reference;
4251
43- if (m_refspecs.empty ())
52+ if (m_branches_flag)
53+ {
54+ auto iter = repo.iterate_branches (GIT_BRANCH_LOCAL);
55+ auto br = iter.next ();
56+ while (br)
57+ {
58+ std::string refspec = " refs/heads/" + std::string (br->name ());
59+ m_refspecs.push_back (refspec);
60+ br = iter.next ();
61+ }
62+ }
63+ else if (m_refspecs.empty ())
4464 {
65+ std::string branch;
4566 try
4667 {
4768 auto head_ref = repo.head ();
48- std::string short_name = head_ref.short_name ();
49- std::string refspec = " refs/heads/" + short_name;
50- m_refspecs.push_back (refspec);
69+ branch = head_ref.short_name ();
5170 }
5271 catch (...)
5372 {
5473 std::cerr << " Could not determine current branch to push." << std::endl;
5574 return ;
5675 }
76+ std::string refspec = " refs/heads/" + branch;
77+ m_refspecs.push_back (refspec);
78+ }
79+ else
80+ {
81+ for (auto & r : m_refspecs)
82+ {
83+ if (r.rfind (" refs/" , 0 ) == 0 )
84+ {
85+ continue ;
86+ }
87+
88+ r = " refs/heads/" + r;
89+ }
5790 }
5891 git_strarray_wrapper refspecs_wrapper (m_refspecs);
59- git_strarray* refspecs_ptr = nullptr ;
60- refspecs_ptr = refspecs_wrapper;
92+ git_strarray* refspecs_ptr = refspecs_wrapper;
93+
94+ // Take a snapshot of repo's references to check which ones are new after push
95+ auto repo_refs = repo.reference_list ();
96+ std::unordered_map<std::string, git_oid> remote_refs_before_oids;
97+ const std::string prefix = std::string (" refs/remotes/" ) + remote_name + " /" ;
98+ for (const auto &r : repo_refs)
99+ {
100+ if (r.size () > prefix.size () && r.compare (0 , prefix.size (), prefix) == 0 )
101+ {
102+ // r is like "refs/remotes/origin/main"
103+ std::string short_name = r.substr (prefix.size ()); // "main" or "feature/x"
104+ std::string canonical_remote_ref = std::string (" refs/heads/" ) + short_name;
105+
106+ git_oid oid = repo.ref_name_to_id (r);
107+ remote_refs_before_oids.emplace (std::move (canonical_remote_ref), oid);
108+ }
109+ }
61110
62111 remote.push (refspecs_ptr, &push_opts);
63- std::cout << " Pushed to " << remote_name << std::endl;
112+
113+ std::cout << " To " << remote.url () << std::endl;
114+ for (const auto & refspec : m_refspecs)
115+ {
116+ std::string_view ref_view (refspec);
117+ std::string_view prefix_local = " refs/heads/" ;
118+ std::string local_short_name;
119+ if (ref_view.size () >= prefix_local.size () && ref_view.substr (0 , prefix_local.size ()) == prefix_local)
120+ {
121+ local_short_name = std::string (ref_view.substr (prefix_local.size ()));
122+ }
123+ else
124+ {
125+ local_short_name = std::string (refspec);
126+ }
127+
128+ std::optional<std::string> upstream_opt = repo.branch_upstream_name (local_short_name);
129+
130+ std::string remote_branch = local_short_name;
131+ std::string canonical_remote_ref = " refs/heads/" + local_short_name;
132+ if (upstream_opt.has_value ())
133+ {
134+ const std::string up_name = upstream_opt.value ();
135+ auto pos = up_name.find (' /' );
136+ if (pos != std::string::npos && pos + 1 < up_name.size ())
137+ {
138+ std::string up_remote = up_name.substr (0 , pos);
139+ std::string up_branch = up_name.substr (pos + 1 );
140+ if (up_remote == remote_name)
141+ {
142+ remote_branch = up_branch;
143+ canonical_remote_ref = " refs/heads/" + remote_branch;
144+ }
145+ }
146+ }
147+
148+ auto it = remote_refs_before_oids.find (canonical_remote_ref);
149+ if (it == remote_refs_before_oids.end ())
150+ {
151+ std::cout << " * [new branch] " << local_short_name << " -> " << remote_branch << std::endl;
152+ continue ;
153+ }
154+
155+ git_oid remote_oid = it->second ;
156+
157+ std::optional<git_oid> local_oid_opt;
158+ if (auto ref_opt = repo.find_reference_dwim ((" refs/heads/" + local_short_name)))
159+ {
160+ const git_oid* target = ref_opt->target ();
161+ local_oid_opt = *target;
162+ }
163+
164+ if (!local_oid_opt)
165+ {
166+ std::cout << " " << local_short_name << " -> " << remote_branch << std::endl;
167+ continue ;
168+ }
169+ git_oid local_oid = local_oid_opt.value ();
170+
171+ if (!git_oid_equal (&remote_oid, &local_oid))
172+ {
173+ std::string old_hex = oid_to_hex (remote_oid);
174+ std::string new_hex = oid_to_hex (local_oid);
175+ // TODO: check order of hex codes
176+ std::cout << " " << old_hex.substr (0 , 7 ) << " .." << new_hex.substr (0 , 7 ) << " "
177+ << local_short_name << " -> " << local_short_name << std::endl;
178+ }
179+ }
64180}
0 commit comments