git-add--interactive.perl 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914
  1. #!/usr/bin/perl
  2. use 5.008;
  3. use strict;
  4. use warnings;
  5. use Git qw(unquote_path);
  6. use Git::I18N;
  7. binmode(STDOUT, ":raw");
  8. my $repo = Git->repository();
  9. my $menu_use_color = $repo->get_colorbool('color.interactive');
  10. my ($prompt_color, $header_color, $help_color) =
  11. $menu_use_color ? (
  12. $repo->get_color('color.interactive.prompt', 'bold blue'),
  13. $repo->get_color('color.interactive.header', 'bold'),
  14. $repo->get_color('color.interactive.help', 'red bold'),
  15. ) : ();
  16. my $error_color = ();
  17. if ($menu_use_color) {
  18. my $help_color_spec = ($repo->config('color.interactive.help') or
  19. 'red bold');
  20. $error_color = $repo->get_color('color.interactive.error',
  21. $help_color_spec);
  22. }
  23. my $diff_use_color = $repo->get_colorbool('color.diff');
  24. my ($fraginfo_color) =
  25. $diff_use_color ? (
  26. $repo->get_color('color.diff.frag', 'cyan'),
  27. ) : ();
  28. my ($diff_plain_color) =
  29. $diff_use_color ? (
  30. $repo->get_color('color.diff.plain', ''),
  31. ) : ();
  32. my ($diff_old_color) =
  33. $diff_use_color ? (
  34. $repo->get_color('color.diff.old', 'red'),
  35. ) : ();
  36. my ($diff_new_color) =
  37. $diff_use_color ? (
  38. $repo->get_color('color.diff.new', 'green'),
  39. ) : ();
  40. my $normal_color = $repo->get_color("", "reset");
  41. my $diff_algorithm = $repo->config('diff.algorithm');
  42. my $diff_filter = $repo->config('interactive.difffilter');
  43. my $use_readkey = 0;
  44. my $use_termcap = 0;
  45. my %term_escapes;
  46. sub ReadMode;
  47. sub ReadKey;
  48. if ($repo->config_bool("interactive.singlekey")) {
  49. eval {
  50. require Term::ReadKey;
  51. Term::ReadKey->import;
  52. $use_readkey = 1;
  53. };
  54. if (!$use_readkey) {
  55. print STDERR "missing Term::ReadKey, disabling interactive.singlekey\n";
  56. }
  57. eval {
  58. require Term::Cap;
  59. my $termcap = Term::Cap->Tgetent;
  60. foreach (values %$termcap) {
  61. $term_escapes{$_} = 1 if /^\e/;
  62. }
  63. $use_termcap = 1;
  64. };
  65. }
  66. sub colored {
  67. my $color = shift;
  68. my $string = join("", @_);
  69. if (defined $color) {
  70. # Put a color code at the beginning of each line, a reset at the end
  71. # color after newlines that are not at the end of the string
  72. $string =~ s/(\n+)(.)/$1$color$2/g;
  73. # reset before newlines
  74. $string =~ s/(\n+)/$normal_color$1/g;
  75. # codes at beginning and end (if necessary):
  76. $string =~ s/^/$color/;
  77. $string =~ s/$/$normal_color/ unless $string =~ /\n$/;
  78. }
  79. return $string;
  80. }
  81. # command line options
  82. my $patch_mode_only;
  83. my $patch_mode;
  84. my $patch_mode_revision;
  85. sub apply_patch;
  86. sub apply_patch_for_checkout_commit;
  87. sub apply_patch_for_stash;
  88. my %patch_modes = (
  89. 'stage' => {
  90. DIFF => 'diff-files -p',
  91. APPLY => sub { apply_patch 'apply --cached', @_; },
  92. APPLY_CHECK => 'apply --cached',
  93. FILTER => 'file-only',
  94. IS_REVERSE => 0,
  95. },
  96. 'stash' => {
  97. DIFF => 'diff-index -p HEAD',
  98. APPLY => sub { apply_patch 'apply --cached', @_; },
  99. APPLY_CHECK => 'apply --cached',
  100. FILTER => undef,
  101. IS_REVERSE => 0,
  102. },
  103. 'reset_head' => {
  104. DIFF => 'diff-index -p --cached',
  105. APPLY => sub { apply_patch 'apply -R --cached', @_; },
  106. APPLY_CHECK => 'apply -R --cached',
  107. FILTER => 'index-only',
  108. IS_REVERSE => 1,
  109. },
  110. 'reset_nothead' => {
  111. DIFF => 'diff-index -R -p --cached',
  112. APPLY => sub { apply_patch 'apply --cached', @_; },
  113. APPLY_CHECK => 'apply --cached',
  114. FILTER => 'index-only',
  115. IS_REVERSE => 0,
  116. },
  117. 'checkout_index' => {
  118. DIFF => 'diff-files -p',
  119. APPLY => sub { apply_patch 'apply -R', @_; },
  120. APPLY_CHECK => 'apply -R',
  121. FILTER => 'file-only',
  122. IS_REVERSE => 1,
  123. },
  124. 'checkout_head' => {
  125. DIFF => 'diff-index -p',
  126. APPLY => sub { apply_patch_for_checkout_commit '-R', @_ },
  127. APPLY_CHECK => 'apply -R',
  128. FILTER => undef,
  129. IS_REVERSE => 1,
  130. },
  131. 'checkout_nothead' => {
  132. DIFF => 'diff-index -R -p',
  133. APPLY => sub { apply_patch_for_checkout_commit '', @_ },
  134. APPLY_CHECK => 'apply',
  135. FILTER => undef,
  136. IS_REVERSE => 0,
  137. },
  138. 'worktree_head' => {
  139. DIFF => 'diff-index -p',
  140. APPLY => sub { apply_patch 'apply -R', @_ },
  141. APPLY_CHECK => 'apply -R',
  142. FILTER => undef,
  143. IS_REVERSE => 1,
  144. },
  145. 'worktree_nothead' => {
  146. DIFF => 'diff-index -R -p',
  147. APPLY => sub { apply_patch 'apply', @_ },
  148. APPLY_CHECK => 'apply',
  149. FILTER => undef,
  150. IS_REVERSE => 0,
  151. },
  152. );
  153. $patch_mode = 'stage';
  154. my %patch_mode_flavour = %{$patch_modes{$patch_mode}};
  155. sub run_cmd_pipe {
  156. if ($^O eq 'MSWin32') {
  157. my @invalid = grep {m/[":*]/} @_;
  158. die "$^O does not support: @invalid\n" if @invalid;
  159. my @args = map { m/ /o ? "\"$_\"": $_ } @_;
  160. return qx{@args};
  161. } else {
  162. my $fh = undef;
  163. open($fh, '-|', @_) or die;
  164. my @out = <$fh>;
  165. close $fh || die "Cannot close @_ ($!)";
  166. return @out;
  167. }
  168. }
  169. my ($GIT_DIR) = run_cmd_pipe(qw(git rev-parse --git-dir));
  170. if (!defined $GIT_DIR) {
  171. exit(1); # rev-parse would have already said "not a git repo"
  172. }
  173. chomp($GIT_DIR);
  174. sub refresh {
  175. my $fh;
  176. open $fh, 'git update-index --refresh |'
  177. or die;
  178. while (<$fh>) {
  179. ;# ignore 'needs update'
  180. }
  181. close $fh;
  182. }
  183. sub list_untracked {
  184. map {
  185. chomp $_;
  186. unquote_path($_);
  187. }
  188. run_cmd_pipe(qw(git ls-files --others --exclude-standard --), @ARGV);
  189. }
  190. # TRANSLATORS: you can adjust this to align "git add -i" status menu
  191. my $status_fmt = __('%12s %12s %s');
  192. my $status_head = sprintf($status_fmt, __('staged'), __('unstaged'), __('path'));
  193. {
  194. my $initial;
  195. sub is_initial_commit {
  196. $initial = system('git rev-parse HEAD -- >/dev/null 2>&1') != 0
  197. unless defined $initial;
  198. return $initial;
  199. }
  200. }
  201. {
  202. my $empty_tree;
  203. sub get_empty_tree {
  204. return $empty_tree if defined $empty_tree;
  205. ($empty_tree) = run_cmd_pipe(qw(git hash-object -t tree /dev/null));
  206. chomp $empty_tree;
  207. return $empty_tree;
  208. }
  209. }
  210. sub get_diff_reference {
  211. my $ref = shift;
  212. if (defined $ref and $ref ne 'HEAD') {
  213. return $ref;
  214. } elsif (is_initial_commit()) {
  215. return get_empty_tree();
  216. } else {
  217. return 'HEAD';
  218. }
  219. }
  220. # Returns list of hashes, contents of each of which are:
  221. # VALUE: pathname
  222. # BINARY: is a binary path
  223. # INDEX: is index different from HEAD?
  224. # FILE: is file different from index?
  225. # INDEX_ADDDEL: is it add/delete between HEAD and index?
  226. # FILE_ADDDEL: is it add/delete between index and file?
  227. # UNMERGED: is the path unmerged
  228. sub list_modified {
  229. my ($only) = @_;
  230. my (%data, @return);
  231. my ($add, $del, $adddel, $file);
  232. my $reference = get_diff_reference($patch_mode_revision);
  233. for (run_cmd_pipe(qw(git diff-index --cached
  234. --numstat --summary), $reference,
  235. '--', @ARGV)) {
  236. if (($add, $del, $file) =
  237. /^([-\d]+) ([-\d]+) (.*)/) {
  238. my ($change, $bin);
  239. $file = unquote_path($file);
  240. if ($add eq '-' && $del eq '-') {
  241. $change = __('binary');
  242. $bin = 1;
  243. }
  244. else {
  245. $change = "+$add/-$del";
  246. }
  247. $data{$file} = {
  248. INDEX => $change,
  249. BINARY => $bin,
  250. FILE => __('nothing'),
  251. }
  252. }
  253. elsif (($adddel, $file) =
  254. /^ (create|delete) mode [0-7]+ (.*)$/) {
  255. $file = unquote_path($file);
  256. $data{$file}{INDEX_ADDDEL} = $adddel;
  257. }
  258. }
  259. for (run_cmd_pipe(qw(git diff-files --ignore-submodules=dirty --numstat --summary --raw --), @ARGV)) {
  260. if (($add, $del, $file) =
  261. /^([-\d]+) ([-\d]+) (.*)/) {
  262. $file = unquote_path($file);
  263. my ($change, $bin);
  264. if ($add eq '-' && $del eq '-') {
  265. $change = __('binary');
  266. $bin = 1;
  267. }
  268. else {
  269. $change = "+$add/-$del";
  270. }
  271. $data{$file}{FILE} = $change;
  272. if ($bin) {
  273. $data{$file}{BINARY} = 1;
  274. }
  275. }
  276. elsif (($adddel, $file) =
  277. /^ (create|delete) mode [0-7]+ (.*)$/) {
  278. $file = unquote_path($file);
  279. $data{$file}{FILE_ADDDEL} = $adddel;
  280. }
  281. elsif (/^:[0-7]+ [0-7]+ [0-9a-f]+ [0-9a-f]+ (.) (.*)$/) {
  282. $file = unquote_path($2);
  283. if (!exists $data{$file}) {
  284. $data{$file} = +{
  285. INDEX => __('unchanged'),
  286. BINARY => 0,
  287. };
  288. }
  289. if ($1 eq 'U') {
  290. $data{$file}{UNMERGED} = 1;
  291. }
  292. }
  293. }
  294. for (sort keys %data) {
  295. my $it = $data{$_};
  296. if ($only) {
  297. if ($only eq 'index-only') {
  298. next if ($it->{INDEX} eq __('unchanged'));
  299. }
  300. if ($only eq 'file-only') {
  301. next if ($it->{FILE} eq __('nothing'));
  302. }
  303. }
  304. push @return, +{
  305. VALUE => $_,
  306. %$it,
  307. };
  308. }
  309. return @return;
  310. }
  311. sub find_unique {
  312. my ($string, @stuff) = @_;
  313. my $found = undef;
  314. for (my $i = 0; $i < @stuff; $i++) {
  315. my $it = $stuff[$i];
  316. my $hit = undef;
  317. if (ref $it) {
  318. if ((ref $it) eq 'ARRAY') {
  319. $it = $it->[0];
  320. }
  321. else {
  322. $it = $it->{VALUE};
  323. }
  324. }
  325. eval {
  326. if ($it =~ /^$string/) {
  327. $hit = 1;
  328. };
  329. };
  330. if (defined $hit && defined $found) {
  331. return undef;
  332. }
  333. if ($hit) {
  334. $found = $i + 1;
  335. }
  336. }
  337. return $found;
  338. }
  339. # inserts string into trie and updates count for each character
  340. sub update_trie {
  341. my ($trie, $string) = @_;
  342. foreach (split //, $string) {
  343. $trie = $trie->{$_} ||= {COUNT => 0};
  344. $trie->{COUNT}++;
  345. }
  346. }
  347. # returns an array of tuples (prefix, remainder)
  348. sub find_unique_prefixes {
  349. my @stuff = @_;
  350. my @return = ();
  351. # any single prefix exceeding the soft limit is omitted
  352. # if any prefix exceeds the hard limit all are omitted
  353. # 0 indicates no limit
  354. my $soft_limit = 0;
  355. my $hard_limit = 3;
  356. # build a trie modelling all possible options
  357. my %trie;
  358. foreach my $print (@stuff) {
  359. if ((ref $print) eq 'ARRAY') {
  360. $print = $print->[0];
  361. }
  362. elsif ((ref $print) eq 'HASH') {
  363. $print = $print->{VALUE};
  364. }
  365. update_trie(\%trie, $print);
  366. push @return, $print;
  367. }
  368. # use the trie to find the unique prefixes
  369. for (my $i = 0; $i < @return; $i++) {
  370. my $ret = $return[$i];
  371. my @letters = split //, $ret;
  372. my %search = %trie;
  373. my ($prefix, $remainder);
  374. my $j;
  375. for ($j = 0; $j < @letters; $j++) {
  376. my $letter = $letters[$j];
  377. if ($search{$letter}{COUNT} == 1) {
  378. $prefix = substr $ret, 0, $j + 1;
  379. $remainder = substr $ret, $j + 1;
  380. last;
  381. }
  382. else {
  383. my $prefix = substr $ret, 0, $j;
  384. return ()
  385. if ($hard_limit && $j + 1 > $hard_limit);
  386. }
  387. %search = %{$search{$letter}};
  388. }
  389. if (ord($letters[0]) > 127 ||
  390. ($soft_limit && $j + 1 > $soft_limit)) {
  391. $prefix = undef;
  392. $remainder = $ret;
  393. }
  394. $return[$i] = [$prefix, $remainder];
  395. }
  396. return @return;
  397. }
  398. # filters out prefixes which have special meaning to list_and_choose()
  399. sub is_valid_prefix {
  400. my $prefix = shift;
  401. return (defined $prefix) &&
  402. !($prefix =~ /[\s,]/) && # separators
  403. !($prefix =~ /^-/) && # deselection
  404. !($prefix =~ /^\d+/) && # selection
  405. ($prefix ne '*') && # "all" wildcard
  406. ($prefix ne '?'); # prompt help
  407. }
  408. # given a prefix/remainder tuple return a string with the prefix highlighted
  409. # for now use square brackets; later might use ANSI colors (underline, bold)
  410. sub highlight_prefix {
  411. my $prefix = shift;
  412. my $remainder = shift;
  413. if (!defined $prefix) {
  414. return $remainder;
  415. }
  416. if (!is_valid_prefix($prefix)) {
  417. return "$prefix$remainder";
  418. }
  419. if (!$menu_use_color) {
  420. return "[$prefix]$remainder";
  421. }
  422. return "$prompt_color$prefix$normal_color$remainder";
  423. }
  424. sub error_msg {
  425. print STDERR colored $error_color, @_;
  426. }
  427. sub list_and_choose {
  428. my ($opts, @stuff) = @_;
  429. my (@chosen, @return);
  430. if (!@stuff) {
  431. return @return;
  432. }
  433. my $i;
  434. my @prefixes = find_unique_prefixes(@stuff) unless $opts->{LIST_ONLY};
  435. TOPLOOP:
  436. while (1) {
  437. my $last_lf = 0;
  438. if ($opts->{HEADER}) {
  439. if (!$opts->{LIST_FLAT}) {
  440. print " ";
  441. }
  442. print colored $header_color, "$opts->{HEADER}\n";
  443. }
  444. for ($i = 0; $i < @stuff; $i++) {
  445. my $chosen = $chosen[$i] ? '*' : ' ';
  446. my $print = $stuff[$i];
  447. my $ref = ref $print;
  448. my $highlighted = highlight_prefix(@{$prefixes[$i]})
  449. if @prefixes;
  450. if ($ref eq 'ARRAY') {
  451. $print = $highlighted || $print->[0];
  452. }
  453. elsif ($ref eq 'HASH') {
  454. my $value = $highlighted || $print->{VALUE};
  455. $print = sprintf($status_fmt,
  456. $print->{INDEX},
  457. $print->{FILE},
  458. $value);
  459. }
  460. else {
  461. $print = $highlighted || $print;
  462. }
  463. printf("%s%2d: %s", $chosen, $i+1, $print);
  464. if (($opts->{LIST_FLAT}) &&
  465. (($i + 1) % ($opts->{LIST_FLAT}))) {
  466. print "\t";
  467. $last_lf = 0;
  468. }
  469. else {
  470. print "\n";
  471. $last_lf = 1;
  472. }
  473. }
  474. if (!$last_lf) {
  475. print "\n";
  476. }
  477. return if ($opts->{LIST_ONLY});
  478. print colored $prompt_color, $opts->{PROMPT};
  479. if ($opts->{SINGLETON}) {
  480. print "> ";
  481. }
  482. else {
  483. print ">> ";
  484. }
  485. my $line = <STDIN>;
  486. if (!$line) {
  487. print "\n";
  488. $opts->{ON_EOF}->() if $opts->{ON_EOF};
  489. last;
  490. }
  491. chomp $line;
  492. last if $line eq '';
  493. if ($line eq '?') {
  494. $opts->{SINGLETON} ?
  495. singleton_prompt_help_cmd() :
  496. prompt_help_cmd();
  497. next TOPLOOP;
  498. }
  499. for my $choice (split(/[\s,]+/, $line)) {
  500. my $choose = 1;
  501. my ($bottom, $top);
  502. # Input that begins with '-'; unchoose
  503. if ($choice =~ s/^-//) {
  504. $choose = 0;
  505. }
  506. # A range can be specified like 5-7 or 5-.
  507. if ($choice =~ /^(\d+)-(\d*)$/) {
  508. ($bottom, $top) = ($1, length($2) ? $2 : 1 + @stuff);
  509. }
  510. elsif ($choice =~ /^\d+$/) {
  511. $bottom = $top = $choice;
  512. }
  513. elsif ($choice eq '*') {
  514. $bottom = 1;
  515. $top = 1 + @stuff;
  516. }
  517. else {
  518. $bottom = $top = find_unique($choice, @stuff);
  519. if (!defined $bottom) {
  520. error_msg sprintf(__("Huh (%s)?\n"), $choice);
  521. next TOPLOOP;
  522. }
  523. }
  524. if ($opts->{SINGLETON} && $bottom != $top) {
  525. error_msg sprintf(__("Huh (%s)?\n"), $choice);
  526. next TOPLOOP;
  527. }
  528. for ($i = $bottom-1; $i <= $top-1; $i++) {
  529. next if (@stuff <= $i || $i < 0);
  530. $chosen[$i] = $choose;
  531. }
  532. }
  533. last if ($opts->{IMMEDIATE} || $line eq '*');
  534. }
  535. for ($i = 0; $i < @stuff; $i++) {
  536. if ($chosen[$i]) {
  537. push @return, $stuff[$i];
  538. }
  539. }
  540. return @return;
  541. }
  542. sub singleton_prompt_help_cmd {
  543. print colored $help_color, __ <<'EOF' ;
  544. Prompt help:
  545. 1 - select a numbered item
  546. foo - select item based on unique prefix
  547. - (empty) select nothing
  548. EOF
  549. }
  550. sub prompt_help_cmd {
  551. print colored $help_color, __ <<'EOF' ;
  552. Prompt help:
  553. 1 - select a single item
  554. 3-5 - select a range of items
  555. 2-3,6-9 - select multiple ranges
  556. foo - select item based on unique prefix
  557. -... - unselect specified items
  558. * - choose all items
  559. - (empty) finish selecting
  560. EOF
  561. }
  562. sub status_cmd {
  563. list_and_choose({ LIST_ONLY => 1, HEADER => $status_head },
  564. list_modified());
  565. print "\n";
  566. }
  567. sub say_n_paths {
  568. my $did = shift @_;
  569. my $cnt = scalar @_;
  570. if ($did eq 'added') {
  571. printf(__n("added %d path\n", "added %d paths\n",
  572. $cnt), $cnt);
  573. } elsif ($did eq 'updated') {
  574. printf(__n("updated %d path\n", "updated %d paths\n",
  575. $cnt), $cnt);
  576. } elsif ($did eq 'reverted') {
  577. printf(__n("reverted %d path\n", "reverted %d paths\n",
  578. $cnt), $cnt);
  579. } else {
  580. printf(__n("touched %d path\n", "touched %d paths\n",
  581. $cnt), $cnt);
  582. }
  583. }
  584. sub update_cmd {
  585. my @mods = list_modified('file-only');
  586. return if (!@mods);
  587. my @update = list_and_choose({ PROMPT => __('Update'),
  588. HEADER => $status_head, },
  589. @mods);
  590. if (@update) {
  591. system(qw(git update-index --add --remove --),
  592. map { $_->{VALUE} } @update);
  593. say_n_paths('updated', @update);
  594. }
  595. print "\n";
  596. }
  597. sub revert_cmd {
  598. my @update = list_and_choose({ PROMPT => __('Revert'),
  599. HEADER => $status_head, },
  600. list_modified());
  601. if (@update) {
  602. if (is_initial_commit()) {
  603. system(qw(git rm --cached),
  604. map { $_->{VALUE} } @update);
  605. }
  606. else {
  607. my @lines = run_cmd_pipe(qw(git ls-tree HEAD --),
  608. map { $_->{VALUE} } @update);
  609. my $fh;
  610. open $fh, '| git update-index --index-info'
  611. or die;
  612. for (@lines) {
  613. print $fh $_;
  614. }
  615. close($fh);
  616. for (@update) {
  617. if ($_->{INDEX_ADDDEL} &&
  618. $_->{INDEX_ADDDEL} eq 'create') {
  619. system(qw(git update-index --force-remove --),
  620. $_->{VALUE});
  621. printf(__("note: %s is untracked now.\n"), $_->{VALUE});
  622. }
  623. }
  624. }
  625. refresh();
  626. say_n_paths('reverted', @update);
  627. }
  628. print "\n";
  629. }
  630. sub add_untracked_cmd {
  631. my @add = list_and_choose({ PROMPT => __('Add untracked') },
  632. list_untracked());
  633. if (@add) {
  634. system(qw(git update-index --add --), @add);
  635. say_n_paths('added', @add);
  636. } else {
  637. print __("No untracked files.\n");
  638. }
  639. print "\n";
  640. }
  641. sub run_git_apply {
  642. my $cmd = shift;
  643. my $fh;
  644. open $fh, '| git ' . $cmd . " --allow-overlap";
  645. print $fh @_;
  646. return close $fh;
  647. }
  648. sub parse_diff {
  649. my ($path) = @_;
  650. my @diff_cmd = split(" ", $patch_mode_flavour{DIFF});
  651. if (defined $diff_algorithm) {
  652. splice @diff_cmd, 1, 0, "--diff-algorithm=${diff_algorithm}";
  653. }
  654. if (defined $patch_mode_revision) {
  655. push @diff_cmd, get_diff_reference($patch_mode_revision);
  656. }
  657. my @diff = run_cmd_pipe("git", @diff_cmd, qw(--no-color --), $path);
  658. my @colored = ();
  659. if ($diff_use_color) {
  660. my @display_cmd = ("git", @diff_cmd, qw(--color --), $path);
  661. if (defined $diff_filter) {
  662. # quotemeta is overkill, but sufficient for shell-quoting
  663. my $diff = join(' ', map { quotemeta } @display_cmd);
  664. @display_cmd = ("$diff | $diff_filter");
  665. }
  666. @colored = run_cmd_pipe(@display_cmd);
  667. }
  668. my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' };
  669. if (@colored && @colored != @diff) {
  670. print STDERR
  671. "fatal: mismatched output from interactive.diffFilter\n",
  672. "hint: Your filter must maintain a one-to-one correspondence\n",
  673. "hint: between its input and output lines.\n";
  674. exit 1;
  675. }
  676. for (my $i = 0; $i < @diff; $i++) {
  677. if ($diff[$i] =~ /^@@ /) {
  678. push @hunk, { TEXT => [], DISPLAY => [],
  679. TYPE => 'hunk' };
  680. }
  681. push @{$hunk[-1]{TEXT}}, $diff[$i];
  682. push @{$hunk[-1]{DISPLAY}},
  683. (@colored ? $colored[$i] : $diff[$i]);
  684. }
  685. return @hunk;
  686. }
  687. sub parse_diff_header {
  688. my $src = shift;
  689. my $head = { TEXT => [], DISPLAY => [], TYPE => 'header' };
  690. my $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' };
  691. my $deletion = { TEXT => [], DISPLAY => [], TYPE => 'deletion' };
  692. my $addition;
  693. for (my $i = 0; $i < @{$src->{TEXT}}; $i++) {
  694. if ($src->{TEXT}->[$i] =~ /^new file/) {
  695. $addition = 1;
  696. $head->{TYPE} = 'addition';
  697. }
  698. my $dest =
  699. $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode :
  700. $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion :
  701. $head;
  702. push @{$dest->{TEXT}}, $src->{TEXT}->[$i];
  703. push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i];
  704. }
  705. return ($head, $mode, $deletion, $addition);
  706. }
  707. sub hunk_splittable {
  708. my ($text) = @_;
  709. my @s = split_hunk($text);
  710. return (1 < @s);
  711. }
  712. sub parse_hunk_header {
  713. my ($line) = @_;
  714. my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
  715. $line =~ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
  716. $o_cnt = 1 unless defined $o_cnt;
  717. $n_cnt = 1 unless defined $n_cnt;
  718. return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);
  719. }
  720. sub format_hunk_header {
  721. my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = @_;
  722. return ("@@ -$o_ofs" .
  723. (($o_cnt != 1) ? ",$o_cnt" : '') .
  724. " +$n_ofs" .
  725. (($n_cnt != 1) ? ",$n_cnt" : '') .
  726. " @@\n");
  727. }
  728. sub split_hunk {
  729. my ($text, $display) = @_;
  730. my @split = ();
  731. if (!defined $display) {
  732. $display = $text;
  733. }
  734. # If there are context lines in the middle of a hunk,
  735. # it can be split, but we would need to take care of
  736. # overlaps later.
  737. my ($o_ofs, undef, $n_ofs) = parse_hunk_header($text->[0]);
  738. my $hunk_start = 1;
  739. OUTER:
  740. while (1) {
  741. my $next_hunk_start = undef;
  742. my $i = $hunk_start - 1;
  743. my $this = +{
  744. TEXT => [],
  745. DISPLAY => [],
  746. TYPE => 'hunk',
  747. OLD => $o_ofs,
  748. NEW => $n_ofs,
  749. OCNT => 0,
  750. NCNT => 0,
  751. ADDDEL => 0,
  752. POSTCTX => 0,
  753. USE => undef,
  754. };
  755. while (++$i < @$text) {
  756. my $line = $text->[$i];
  757. my $display = $display->[$i];
  758. if ($line =~ /^\\/) {
  759. push @{$this->{TEXT}}, $line;
  760. push @{$this->{DISPLAY}}, $display;
  761. next;
  762. }
  763. if ($line =~ /^ /) {
  764. if ($this->{ADDDEL} &&
  765. !defined $next_hunk_start) {
  766. # We have seen leading context and
  767. # adds/dels and then here is another
  768. # context, which is trailing for this
  769. # split hunk and leading for the next
  770. # one.
  771. $next_hunk_start = $i;
  772. }
  773. push @{$this->{TEXT}}, $line;
  774. push @{$this->{DISPLAY}}, $display;
  775. $this->{OCNT}++;
  776. $this->{NCNT}++;
  777. if (defined $next_hunk_start) {
  778. $this->{POSTCTX}++;
  779. }
  780. next;
  781. }
  782. # add/del
  783. if (defined $next_hunk_start) {
  784. # We are done with the current hunk and
  785. # this is the first real change for the
  786. # next split one.
  787. $hunk_start = $next_hunk_start;
  788. $o_ofs = $this->{OLD} + $this->{OCNT};
  789. $n_ofs = $this->{NEW} + $this->{NCNT};
  790. $o_ofs -= $this->{POSTCTX};
  791. $n_ofs -= $this->{POSTCTX};
  792. push @split, $this;
  793. redo OUTER;
  794. }
  795. push @{$this->{TEXT}}, $line;
  796. push @{$this->{DISPLAY}}, $display;
  797. $this->{ADDDEL}++;
  798. if ($line =~ /^-/) {
  799. $this->{OCNT}++;
  800. }
  801. else {
  802. $this->{NCNT}++;
  803. }
  804. }
  805. push @split, $this;
  806. last;
  807. }
  808. for my $hunk (@split) {
  809. $o_ofs = $hunk->{OLD};
  810. $n_ofs = $hunk->{NEW};
  811. my $o_cnt = $hunk->{OCNT};
  812. my $n_cnt = $hunk->{NCNT};
  813. my $head = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
  814. my $display_head = $head;
  815. unshift @{$hunk->{TEXT}}, $head;
  816. if ($diff_use_color) {
  817. $display_head = colored($fraginfo_color, $head);
  818. }
  819. unshift @{$hunk->{DISPLAY}}, $display_head;
  820. }
  821. return @split;
  822. }
  823. sub find_last_o_ctx {
  824. my ($it) = @_;
  825. my $text = $it->{TEXT};
  826. my ($o_ofs, $o_cnt) = parse_hunk_header($text->[0]);
  827. my $i = @{$text};
  828. my $last_o_ctx = $o_ofs + $o_cnt;
  829. while (0 < --$i) {
  830. my $line = $text->[$i];
  831. if ($line =~ /^ /) {
  832. $last_o_ctx--;
  833. next;
  834. }
  835. last;
  836. }
  837. return $last_o_ctx;
  838. }
  839. sub merge_hunk {
  840. my ($prev, $this) = @_;
  841. my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) =
  842. parse_hunk_header($prev->{TEXT}[0]);
  843. my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) =
  844. parse_hunk_header($this->{TEXT}[0]);
  845. my (@line, $i, $ofs, $o_cnt, $n_cnt);
  846. $ofs = $o0_ofs;
  847. $o_cnt = $n_cnt = 0;
  848. for ($i = 1; $i < @{$prev->{TEXT}}; $i++) {
  849. my $line = $prev->{TEXT}[$i];
  850. if ($line =~ /^\+/) {
  851. $n_cnt++;
  852. push @line, $line;
  853. next;
  854. } elsif ($line =~ /^\\/) {
  855. push @line, $line;
  856. next;
  857. }
  858. last if ($o1_ofs <= $ofs);
  859. $o_cnt++;
  860. $ofs++;
  861. if ($line =~ /^ /) {
  862. $n_cnt++;
  863. }
  864. push @line, $line;
  865. }
  866. for ($i = 1; $i < @{$this->{TEXT}}; $i++) {
  867. my $line = $this->{TEXT}[$i];
  868. if ($line =~ /^\+/) {
  869. $n_cnt++;
  870. push @line, $line;
  871. next;
  872. } elsif ($line =~ /^\\/) {
  873. push @line, $line;
  874. next;
  875. }
  876. $ofs++;
  877. $o_cnt++;
  878. if ($line =~ /^ /) {
  879. $n_cnt++;
  880. }
  881. push @line, $line;
  882. }
  883. my $head = format_hunk_header($o0_ofs, $o_cnt, $n0_ofs, $n_cnt);
  884. @{$prev->{TEXT}} = ($head, @line);
  885. }
  886. sub coalesce_overlapping_hunks {
  887. my (@in) = @_;
  888. my @out = ();
  889. my ($last_o_ctx, $last_was_dirty);
  890. my $ofs_delta = 0;
  891. for (@in) {
  892. if ($_->{TYPE} ne 'hunk') {
  893. push @out, $_;
  894. next;
  895. }
  896. my $text = $_->{TEXT};
  897. my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
  898. parse_hunk_header($text->[0]);
  899. unless ($_->{USE}) {
  900. $ofs_delta += $o_cnt - $n_cnt;
  901. # If this hunk has been edited then subtract
  902. # the delta that is due to the edit.
  903. if ($_->{OFS_DELTA}) {
  904. $ofs_delta -= $_->{OFS_DELTA};
  905. }
  906. next;
  907. }
  908. if ($ofs_delta) {
  909. if ($patch_mode_flavour{IS_REVERSE}) {
  910. $o_ofs -= $ofs_delta;
  911. } else {
  912. $n_ofs += $ofs_delta;
  913. }
  914. $_->{TEXT}->[0] = format_hunk_header($o_ofs, $o_cnt,
  915. $n_ofs, $n_cnt);
  916. }
  917. # If this hunk was edited then adjust the offset delta
  918. # to reflect the edit.
  919. if ($_->{OFS_DELTA}) {
  920. $ofs_delta += $_->{OFS_DELTA};
  921. }
  922. if (defined $last_o_ctx &&
  923. $o_ofs <= $last_o_ctx &&
  924. !$_->{DIRTY} &&
  925. !$last_was_dirty) {
  926. merge_hunk($out[-1], $_);
  927. }
  928. else {
  929. push @out, $_;
  930. }
  931. $last_o_ctx = find_last_o_ctx($out[-1]);
  932. $last_was_dirty = $_->{DIRTY};
  933. }
  934. return @out;
  935. }
  936. sub reassemble_patch {
  937. my $head = shift;
  938. my @patch;
  939. # Include everything in the header except the beginning of the diff.
  940. push @patch, (grep { !/^[-+]{3}/ } @$head);
  941. # Then include any headers from the hunk lines, which must
  942. # come before any actual hunk.
  943. while (@_ && $_[0] !~ /^@/) {
  944. push @patch, shift;
  945. }
  946. # Then begin the diff.
  947. push @patch, grep { /^[-+]{3}/ } @$head;
  948. # And then the actual hunks.
  949. push @patch, @_;
  950. return @patch;
  951. }
  952. sub color_diff {
  953. return map {
  954. colored((/^@/ ? $fraginfo_color :
  955. /^\+/ ? $diff_new_color :
  956. /^-/ ? $diff_old_color :
  957. $diff_plain_color),
  958. $_);
  959. } @_;
  960. }
  961. my %edit_hunk_manually_modes = (
  962. stage => N__(
  963. "If the patch applies cleanly, the edited hunk will immediately be
  964. marked for staging."),
  965. stash => N__(
  966. "If the patch applies cleanly, the edited hunk will immediately be
  967. marked for stashing."),
  968. reset_head => N__(
  969. "If the patch applies cleanly, the edited hunk will immediately be
  970. marked for unstaging."),
  971. reset_nothead => N__(
  972. "If the patch applies cleanly, the edited hunk will immediately be
  973. marked for applying."),
  974. checkout_index => N__(
  975. "If the patch applies cleanly, the edited hunk will immediately be
  976. marked for discarding."),
  977. checkout_head => N__(
  978. "If the patch applies cleanly, the edited hunk will immediately be
  979. marked for discarding."),
  980. checkout_nothead => N__(
  981. "If the patch applies cleanly, the edited hunk will immediately be
  982. marked for applying."),
  983. worktree_head => N__(
  984. "If the patch applies cleanly, the edited hunk will immediately be
  985. marked for discarding."),
  986. worktree_nothead => N__(
  987. "If the patch applies cleanly, the edited hunk will immediately be
  988. marked for applying."),
  989. );
  990. sub recount_edited_hunk {
  991. local $_;
  992. my ($oldtext, $newtext) = @_;
  993. my ($o_cnt, $n_cnt) = (0, 0);
  994. for (@{$newtext}[1..$#{$newtext}]) {
  995. my $mode = substr($_, 0, 1);
  996. if ($mode eq '-') {
  997. $o_cnt++;
  998. } elsif ($mode eq '+') {
  999. $n_cnt++;
  1000. } elsif ($mode eq ' ' or $mode eq "\n") {
  1001. $o_cnt++;
  1002. $n_cnt++;
  1003. }
  1004. }
  1005. my ($o_ofs, undef, $n_ofs, undef) =
  1006. parse_hunk_header($newtext->[0]);
  1007. $newtext->[0] = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
  1008. my (undef, $orig_o_cnt, undef, $orig_n_cnt) =
  1009. parse_hunk_header($oldtext->[0]);
  1010. # Return the change in the number of lines inserted by this hunk
  1011. return $orig_o_cnt - $orig_n_cnt - $o_cnt + $n_cnt;
  1012. }
  1013. sub edit_hunk_manually {
  1014. my ($oldtext) = @_;
  1015. my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff";
  1016. my $fh;
  1017. open $fh, '>', $hunkfile
  1018. or die sprintf(__("failed to open hunk edit file for writing: %s"), $!);
  1019. print $fh Git::comment_lines __("Manual hunk edit mode -- see bottom for a quick guide.\n");
  1020. print $fh @$oldtext;
  1021. my $is_reverse = $patch_mode_flavour{IS_REVERSE};
  1022. my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-');
  1023. my $comment_line_char = Git::get_comment_line_char;
  1024. print $fh Git::comment_lines sprintf(__ <<EOF, $remove_minus, $remove_plus, $comment_line_char),
  1025. ---
  1026. To remove '%s' lines, make them ' ' lines (context).
  1027. To remove '%s' lines, delete them.
  1028. Lines starting with %s will be removed.
  1029. EOF
  1030. __($edit_hunk_manually_modes{$patch_mode}),
  1031. # TRANSLATORS: 'it' refers to the patch mentioned in the previous messages.
  1032. __ <<EOF2 ;
  1033. If it does not apply cleanly, you will be given an opportunity to
  1034. edit again. If all lines of the hunk are removed, then the edit is
  1035. aborted and the hunk is left unchanged.
  1036. EOF2
  1037. close $fh;
  1038. chomp(my ($editor) = run_cmd_pipe(qw(git var GIT_EDITOR)));
  1039. system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
  1040. if ($? != 0) {
  1041. return undef;
  1042. }
  1043. open $fh, '<', $hunkfile
  1044. or die sprintf(__("failed to open hunk edit file for reading: %s"), $!);
  1045. my @newtext = grep { !/^\Q$comment_line_char\E/ } <$fh>;
  1046. close $fh;
  1047. unlink $hunkfile;
  1048. # Abort if nothing remains
  1049. if (!grep { /\S/ } @newtext) {
  1050. return undef;
  1051. }
  1052. # Reinsert the first hunk header if the user accidentally deleted it
  1053. if ($newtext[0] !~ /^@/) {
  1054. unshift @newtext, $oldtext->[0];
  1055. }
  1056. return \@newtext;
  1057. }
  1058. sub diff_applies {
  1059. return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --check',
  1060. map { @{$_->{TEXT}} } @_);
  1061. }
  1062. sub _restore_terminal_and_die {
  1063. ReadMode 'restore';
  1064. print "\n";
  1065. exit 1;
  1066. }
  1067. sub prompt_single_character {
  1068. if ($use_readkey) {
  1069. local $SIG{TERM} = \&_restore_terminal_and_die;
  1070. local $SIG{INT} = \&_restore_terminal_and_die;
  1071. ReadMode 'cbreak';
  1072. my $key = ReadKey 0;
  1073. ReadMode 'restore';
  1074. if ($use_termcap and $key eq "\e") {
  1075. while (!defined $term_escapes{$key}) {
  1076. my $next = ReadKey 0.5;
  1077. last if (!defined $next);
  1078. $key .= $next;
  1079. }
  1080. $key =~ s/\e/^[/;
  1081. }
  1082. print "$key" if defined $key;
  1083. print "\n";
  1084. return $key;
  1085. } else {
  1086. return <STDIN>;
  1087. }
  1088. }
  1089. sub prompt_yesno {
  1090. my ($prompt) = @_;
  1091. while (1) {
  1092. print colored $prompt_color, $prompt;
  1093. my $line = prompt_single_character;
  1094. return undef unless defined $line;
  1095. return 0 if $line =~ /^n/i;
  1096. return 1 if $line =~ /^y/i;
  1097. }
  1098. }
  1099. sub edit_hunk_loop {
  1100. my ($head, $hunks, $ix) = @_;
  1101. my $hunk = $hunks->[$ix];
  1102. my $text = $hunk->{TEXT};
  1103. while (1) {
  1104. my $newtext = edit_hunk_manually($text);
  1105. if (!defined $newtext) {
  1106. return undef;
  1107. }
  1108. my $newhunk = {
  1109. TEXT => $newtext,
  1110. TYPE => $hunk->{TYPE},
  1111. USE => 1,
  1112. DIRTY => 1,
  1113. };
  1114. $newhunk->{OFS_DELTA} = recount_edited_hunk($text, $newtext);
  1115. # If this hunk has already been edited then add the
  1116. # offset delta of the previous edit to get the real
  1117. # delta from the original unedited hunk.
  1118. $hunk->{OFS_DELTA} and
  1119. $newhunk->{OFS_DELTA} += $hunk->{OFS_DELTA};
  1120. if (diff_applies($head,
  1121. @{$hunks}[0..$ix-1],
  1122. $newhunk,
  1123. @{$hunks}[$ix+1..$#{$hunks}])) {
  1124. $newhunk->{DISPLAY} = [color_diff(@{$newtext})];
  1125. return $newhunk;
  1126. }
  1127. else {
  1128. prompt_yesno(
  1129. # TRANSLATORS: do not translate [y/n]
  1130. # The program will only accept that input
  1131. # at this point.
  1132. # Consider translating (saying "no" discards!) as
  1133. # (saying "n" for "no" discards!) if the translation
  1134. # of the word "no" does not start with n.
  1135. __('Your edited hunk does not apply. Edit again '
  1136. . '(saying "no" discards!) [y/n]? ')
  1137. ) or return undef;
  1138. }
  1139. }
  1140. }
  1141. my %help_patch_modes = (
  1142. stage => N__(
  1143. "y - stage this hunk
  1144. n - do not stage this hunk
  1145. q - quit; do not stage this hunk or any of the remaining ones
  1146. a - stage this hunk and all later hunks in the file
  1147. d - do not stage this hunk or any of the later hunks in the file"),
  1148. stash => N__(
  1149. "y - stash this hunk
  1150. n - do not stash this hunk
  1151. q - quit; do not stash this hunk or any of the remaining ones
  1152. a - stash this hunk and all later hunks in the file
  1153. d - do not stash this hunk or any of the later hunks in the file"),
  1154. reset_head => N__(
  1155. "y - unstage this hunk
  1156. n - do not unstage this hunk
  1157. q - quit; do not unstage this hunk or any of the remaining ones
  1158. a - unstage this hunk and all later hunks in the file
  1159. d - do not unstage this hunk or any of the later hunks in the file"),
  1160. reset_nothead => N__(
  1161. "y - apply this hunk to index
  1162. n - do not apply this hunk to index
  1163. q - quit; do not apply this hunk or any of the remaining ones
  1164. a - apply this hunk and all later hunks in the file
  1165. d - do not apply this hunk or any of the later hunks in the file"),
  1166. checkout_index => N__(
  1167. "y - discard this hunk from worktree
  1168. n - do not discard this hunk from worktree
  1169. q - quit; do not discard this hunk or any of the remaining ones
  1170. a - discard this hunk and all later hunks in the file
  1171. d - do not discard this hunk or any of the later hunks in the file"),
  1172. checkout_head => N__(
  1173. "y - discard this hunk from index and worktree
  1174. n - do not discard this hunk from index and worktree
  1175. q - quit; do not discard this hunk or any of the remaining ones
  1176. a - discard this hunk and all later hunks in the file
  1177. d - do not discard this hunk or any of the later hunks in the file"),
  1178. checkout_nothead => N__(
  1179. "y - apply this hunk to index and worktree
  1180. n - do not apply this hunk to index and worktree
  1181. q - quit; do not apply this hunk or any of the remaining ones
  1182. a - apply this hunk and all later hunks in the file
  1183. d - do not apply this hunk or any of the later hunks in the file"),
  1184. worktree_head => N__(
  1185. "y - discard this hunk from worktree
  1186. n - do not discard this hunk from worktree
  1187. q - quit; do not discard this hunk or any of the remaining ones
  1188. a - discard this hunk and all later hunks in the file
  1189. d - do not discard this hunk or any of the later hunks in the file"),
  1190. worktree_nothead => N__(
  1191. "y - apply this hunk to worktree
  1192. n - do not apply this hunk to worktree
  1193. q - quit; do not apply this hunk or any of the remaining ones
  1194. a - apply this hunk and all later hunks in the file
  1195. d - do not apply this hunk or any of the later hunks in the file"),
  1196. );
  1197. sub help_patch_cmd {
  1198. local $_;
  1199. my $other = $_[0] . ",?";
  1200. print colored $help_color, __($help_patch_modes{$patch_mode}), "\n",
  1201. map { "$_\n" } grep {
  1202. my $c = quotemeta(substr($_, 0, 1));
  1203. $other =~ /,$c/
  1204. } split "\n", __ <<EOF ;
  1205. g - select a hunk to go to
  1206. / - search for a hunk matching the given regex
  1207. j - leave this hunk undecided, see next undecided hunk
  1208. J - leave this hunk undecided, see next hunk
  1209. k - leave this hunk undecided, see previous undecided hunk
  1210. K - leave this hunk undecided, see previous hunk
  1211. s - split the current hunk into smaller hunks
  1212. e - manually edit the current hunk
  1213. ? - print help
  1214. EOF
  1215. }
  1216. sub apply_patch {
  1217. my $cmd = shift;
  1218. my $ret = run_git_apply $cmd, @_;
  1219. if (!$ret) {
  1220. print STDERR @_;
  1221. }
  1222. return $ret;
  1223. }
  1224. sub apply_patch_for_checkout_commit {
  1225. my $reverse = shift;
  1226. my $applies_index = run_git_apply 'apply '.$reverse.' --cached --check', @_;
  1227. my $applies_worktree = run_git_apply 'apply '.$reverse.' --check', @_;
  1228. if ($applies_worktree && $applies_index) {
  1229. run_git_apply 'apply '.$reverse.' --cached', @_;
  1230. run_git_apply 'apply '.$reverse, @_;
  1231. return 1;
  1232. } elsif (!$applies_index) {
  1233. print colored $error_color, __("The selected hunks do not apply to the index!\n");
  1234. if (prompt_yesno __("Apply them to the worktree anyway? ")) {
  1235. return run_git_apply 'apply '.$reverse, @_;
  1236. } else {
  1237. print colored $error_color, __("Nothing was applied.\n");
  1238. return 0;
  1239. }
  1240. } else {
  1241. print STDERR @_;
  1242. return 0;
  1243. }
  1244. }
  1245. sub patch_update_cmd {
  1246. my @all_mods = list_modified($patch_mode_flavour{FILTER});
  1247. error_msg sprintf(__("ignoring unmerged: %s\n"), $_->{VALUE})
  1248. for grep { $_->{UNMERGED} } @all_mods;
  1249. @all_mods = grep { !$_->{UNMERGED} } @all_mods;
  1250. my @mods = grep { !($_->{BINARY}) } @all_mods;
  1251. my @them;
  1252. if (!@mods) {
  1253. if (@all_mods) {
  1254. print STDERR __("Only binary files changed.\n");
  1255. } else {
  1256. print STDERR __("No changes.\n");
  1257. }
  1258. return 0;
  1259. }
  1260. if ($patch_mode_only) {
  1261. @them = @mods;
  1262. }
  1263. else {
  1264. @them = list_and_choose({ PROMPT => __('Patch update'),
  1265. HEADER => $status_head, },
  1266. @mods);
  1267. }
  1268. for (@them) {
  1269. return 0 if patch_update_file($_->{VALUE});
  1270. }
  1271. }
  1272. # Generate a one line summary of a hunk.
  1273. sub summarize_hunk {
  1274. my $rhunk = shift;
  1275. my $summary = $rhunk->{TEXT}[0];
  1276. # Keep the line numbers, discard extra context.
  1277. $summary =~ s/@@(.*?)@@.*/$1 /s;
  1278. $summary .= " " x (20 - length $summary);
  1279. # Add some user context.
  1280. for my $line (@{$rhunk->{TEXT}}) {
  1281. if ($line =~ m/^[+-].*\w/) {
  1282. $summary .= $line;
  1283. last;
  1284. }
  1285. }
  1286. chomp $summary;
  1287. return substr($summary, 0, 80) . "\n";
  1288. }
  1289. # Print a one-line summary of each hunk in the array ref in
  1290. # the first argument, starting with the index in the 2nd.
  1291. sub display_hunks {
  1292. my ($hunks, $i) = @_;
  1293. my $ctr = 0;
  1294. $i ||= 0;
  1295. for (; $i < @$hunks && $ctr < 20; $i++, $ctr++) {
  1296. my $status = " ";
  1297. if (defined $hunks->[$i]{USE}) {
  1298. $status = $hunks->[$i]{USE} ? "+" : "-";
  1299. }
  1300. printf "%s%2d: %s",
  1301. $status,
  1302. $i + 1,
  1303. summarize_hunk($hunks->[$i]);
  1304. }
  1305. return $i;
  1306. }
  1307. my %patch_update_prompt_modes = (
  1308. stage => {
  1309. mode => N__("Stage mode change [y,n,q,a,d%s,?]? "),
  1310. deletion => N__("Stage deletion [y,n,q,a,d%s,?]? "),
  1311. addition => N__("Stage addition [y,n,q,a,d%s,?]? "),
  1312. hunk => N__("Stage this hunk [y,n,q,a,d%s,?]? "),
  1313. },
  1314. stash => {
  1315. mode => N__("Stash mode change [y,n,q,a,d%s,?]? "),
  1316. deletion => N__("Stash deletion [y,n,q,a,d%s,?]? "),
  1317. addition => N__("Stash addition [y,n,q,a,d%s,?]? "),
  1318. hunk => N__("Stash this hunk [y,n,q,a,d%s,?]? "),
  1319. },
  1320. reset_head => {
  1321. mode => N__("Unstage mode change [y,n,q,a,d%s,?]? "),
  1322. deletion => N__("Unstage deletion [y,n,q,a,d%s,?]? "),
  1323. addition => N__("Unstage addition [y,n,q,a,d%s,?]? "),
  1324. hunk => N__("Unstage this hunk [y,n,q,a,d%s,?]? "),
  1325. },
  1326. reset_nothead => {
  1327. mode => N__("Apply mode change to index [y,n,q,a,d%s,?]? "),
  1328. deletion => N__("Apply deletion to index [y,n,q,a,d%s,?]? "),
  1329. addition => N__("Apply addition to index [y,n,q,a,d%s,?]? "),
  1330. hunk => N__("Apply this hunk to index [y,n,q,a,d%s,?]? "),
  1331. },
  1332. checkout_index => {
  1333. mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
  1334. deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
  1335. addition => N__("Discard addition from worktree [y,n,q,a,d%s,?]? "),
  1336. hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
  1337. },
  1338. checkout_head => {
  1339. mode => N__("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
  1340. deletion => N__("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
  1341. addition => N__("Discard addition from index and worktree [y,n,q,a,d%s,?]? "),
  1342. hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
  1343. },
  1344. checkout_nothead => {
  1345. mode => N__("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
  1346. deletion => N__("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
  1347. addition => N__("Apply addition to index and worktree [y,n,q,a,d%s,?]? "),
  1348. hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
  1349. },
  1350. worktree_head => {
  1351. mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
  1352. deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
  1353. addition => N__("Discard addition from worktree [y,n,q,a,d%s,?]? "),
  1354. hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
  1355. },
  1356. worktree_nothead => {
  1357. mode => N__("Apply mode change to worktree [y,n,q,a,d%s,?]? "),
  1358. deletion => N__("Apply deletion to worktree [y,n,q,a,d%s,?]? "),
  1359. addition => N__("Apply addition to worktree [y,n,q,a,d%s,?]? "),
  1360. hunk => N__("Apply this hunk to worktree [y,n,q,a,d%s,?]? "),
  1361. },
  1362. );
  1363. sub patch_update_file {
  1364. my $quit = 0;
  1365. my ($ix, $num);
  1366. my $path = shift;
  1367. my ($head, @hunk) = parse_diff($path);
  1368. ($head, my $mode, my $deletion, my $addition) = parse_diff_header($head);
  1369. for (@{$head->{DISPLAY}}) {
  1370. print;
  1371. }
  1372. if (@{$mode->{TEXT}}) {
  1373. unshift @hunk, $mode;
  1374. }
  1375. if (@{$deletion->{TEXT}}) {
  1376. foreach my $hunk (@hunk) {
  1377. push @{$deletion->{TEXT}}, @{$hunk->{TEXT}};
  1378. push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}};
  1379. }
  1380. @hunk = ($deletion);
  1381. }
  1382. $num = scalar @hunk;
  1383. $ix = 0;
  1384. while (1) {
  1385. my ($prev, $next, $other, $undecided, $i);
  1386. $other = '';
  1387. last if ($ix and !$num);
  1388. if ($num <= $ix) {
  1389. $ix = 0;
  1390. }
  1391. for ($i = 0; $i < $ix; $i++) {
  1392. if (!defined $hunk[$i]{USE}) {
  1393. $prev = 1;
  1394. $other .= ',k';
  1395. last;
  1396. }
  1397. }
  1398. if ($ix) {
  1399. $other .= ',K';
  1400. }
  1401. for ($i = $ix + 1; $i < $num; $i++) {
  1402. if (!defined $hunk[$i]{USE}) {
  1403. $next = 1;
  1404. $other .= ',j';
  1405. last;
  1406. }
  1407. }
  1408. if ($ix < $num - 1) {
  1409. $other .= ',J';
  1410. }
  1411. if ($num > 1) {
  1412. $other .= ',g,/';
  1413. }
  1414. for ($i = 0; $i < $num; $i++) {
  1415. if (!defined $hunk[$i]{USE}) {
  1416. $undecided = 1;
  1417. last;
  1418. }
  1419. }
  1420. last if (!$undecided && ($num || !$addition));
  1421. if ($num) {
  1422. if ($hunk[$ix]{TYPE} eq 'hunk' &&
  1423. hunk_splittable($hunk[$ix]{TEXT})) {
  1424. $other .= ',s';
  1425. }
  1426. if ($hunk[$ix]{TYPE} eq 'hunk') {
  1427. $other .= ',e';
  1428. }
  1429. for (@{$hunk[$ix]{DISPLAY}}) {
  1430. print;
  1431. }
  1432. }
  1433. my $type = $num ? $hunk[$ix]{TYPE} : $head->{TYPE};
  1434. print colored $prompt_color, "(", ($ix+1), "/", ($num ? $num : 1), ") ",
  1435. sprintf(__($patch_update_prompt_modes{$patch_mode}{$type}), $other);
  1436. my $line = prompt_single_character;
  1437. last unless defined $line;
  1438. if ($line) {
  1439. if ($line =~ /^y/i) {
  1440. if ($num) {
  1441. $hunk[$ix]{USE} = 1;
  1442. } else {
  1443. $head->{USE} = 1;
  1444. }
  1445. }
  1446. elsif ($line =~ /^n/i) {
  1447. if ($num) {
  1448. $hunk[$ix]{USE} = 0;
  1449. } else {
  1450. $head->{USE} = 0;
  1451. }
  1452. }
  1453. elsif ($line =~ /^a/i) {
  1454. if ($num) {
  1455. while ($ix < $num) {
  1456. if (!defined $hunk[$ix]{USE}) {
  1457. $hunk[$ix]{USE} = 1;
  1458. }
  1459. $ix++;
  1460. }
  1461. } else {
  1462. $head->{USE} = 1;
  1463. $ix++;
  1464. }
  1465. next;
  1466. }
  1467. elsif ($line =~ /^g(.*)/) {
  1468. my $response = $1;
  1469. unless ($other =~ /g/) {
  1470. error_msg __("No other hunks to goto\n");
  1471. next;
  1472. }
  1473. my $no = $ix > 10 ? $ix - 10 : 0;
  1474. while ($response eq '') {
  1475. $no = display_hunks(\@hunk, $no);
  1476. if ($no < $num) {
  1477. print __("go to which hunk (<ret> to see more)? ");
  1478. } else {
  1479. print __("go to which hunk? ");
  1480. }
  1481. $response = <STDIN>;
  1482. if (!defined $response) {
  1483. $response = '';
  1484. }
  1485. chomp $response;
  1486. }
  1487. if ($response !~ /^\s*\d+\s*$/) {
  1488. error_msg sprintf(__("Invalid number: '%s'\n"),
  1489. $response);
  1490. } elsif (0 < $response && $response <= $num) {
  1491. $ix = $response - 1;
  1492. } else {
  1493. error_msg sprintf(__n("Sorry, only %d hunk available.\n",
  1494. "Sorry, only %d hunks available.\n", $num), $num);
  1495. }
  1496. next;
  1497. }
  1498. elsif ($line =~ /^d/i) {
  1499. if ($num) {
  1500. while ($ix < $num) {
  1501. if (!defined $hunk[$ix]{USE}) {
  1502. $hunk[$ix]{USE} = 0;
  1503. }
  1504. $ix++;
  1505. }
  1506. } else {
  1507. $head->{USE} = 0;
  1508. $ix++;
  1509. }
  1510. next;
  1511. }
  1512. elsif ($line =~ /^q/i) {
  1513. if ($num) {
  1514. for ($i = 0; $i < $num; $i++) {
  1515. if (!defined $hunk[$i]{USE}) {
  1516. $hunk[$i]{USE} = 0;
  1517. }
  1518. }
  1519. } elsif (!defined $head->{USE}) {
  1520. $head->{USE} = 0;
  1521. }
  1522. $quit = 1;
  1523. last;
  1524. }
  1525. elsif ($line =~ m|^/(.*)|) {
  1526. my $regex = $1;
  1527. unless ($other =~ m|/|) {
  1528. error_msg __("No other hunks to search\n");
  1529. next;
  1530. }
  1531. if ($regex eq "") {
  1532. print colored $prompt_color, __("search for regex? ");
  1533. $regex = <STDIN>;
  1534. if (defined $regex) {
  1535. chomp $regex;
  1536. }
  1537. }
  1538. my $search_string;
  1539. eval {
  1540. $search_string = qr{$regex}m;
  1541. };
  1542. if ($@) {
  1543. my ($err,$exp) = ($@, $1);
  1544. $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
  1545. error_msg sprintf(__("Malformed search regexp %s: %s\n"), $exp, $err);
  1546. next;
  1547. }
  1548. my $iy = $ix;
  1549. while (1) {
  1550. my $text = join ("", @{$hunk[$iy]{TEXT}});
  1551. last if ($text =~ $search_string);
  1552. $iy++;
  1553. $iy = 0 if ($iy >= $num);
  1554. if ($ix == $iy) {
  1555. error_msg __("No hunk matches the given pattern\n");
  1556. last;
  1557. }
  1558. }
  1559. $ix = $iy;
  1560. next;
  1561. }
  1562. elsif ($line =~ /^K/) {
  1563. if ($other =~ /K/) {
  1564. $ix--;
  1565. }
  1566. else {
  1567. error_msg __("No previous hunk\n");
  1568. }
  1569. next;
  1570. }
  1571. elsif ($line =~ /^J/) {
  1572. if ($other =~ /J/) {
  1573. $ix++;
  1574. }
  1575. else {
  1576. error_msg __("No next hunk\n");
  1577. }
  1578. next;
  1579. }
  1580. elsif ($line =~ /^k/) {
  1581. if ($other =~ /k/) {
  1582. while (1) {
  1583. $ix--;
  1584. last if (!$ix ||
  1585. !defined $hunk[$ix]{USE});
  1586. }
  1587. }
  1588. else {
  1589. error_msg __("No previous hunk\n");
  1590. }
  1591. next;
  1592. }
  1593. elsif ($line =~ /^j/) {
  1594. if ($other !~ /j/) {
  1595. error_msg __("No next hunk\n");
  1596. next;
  1597. }
  1598. }
  1599. elsif ($line =~ /^s/) {
  1600. unless ($other =~ /s/) {
  1601. error_msg __("Sorry, cannot split this hunk\n");
  1602. next;
  1603. }
  1604. my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY});
  1605. if (1 < @split) {
  1606. print colored $header_color, sprintf(
  1607. __n("Split into %d hunk.\n",
  1608. "Split into %d hunks.\n",
  1609. scalar(@split)), scalar(@split));
  1610. }
  1611. splice (@hunk, $ix, 1, @split);
  1612. $num = scalar @hunk;
  1613. next;
  1614. }
  1615. elsif ($line =~ /^e/) {
  1616. unless ($other =~ /e/) {
  1617. error_msg __("Sorry, cannot edit this hunk\n");
  1618. next;
  1619. }
  1620. my $newhunk = edit_hunk_loop($head, \@hunk, $ix);
  1621. if (defined $newhunk) {
  1622. splice @hunk, $ix, 1, $newhunk;
  1623. }
  1624. }
  1625. else {
  1626. help_patch_cmd($other);
  1627. next;
  1628. }
  1629. # soft increment
  1630. while (1) {
  1631. $ix++;
  1632. last if ($ix >= $num ||
  1633. !defined $hunk[$ix]{USE});
  1634. }
  1635. }
  1636. }
  1637. @hunk = coalesce_overlapping_hunks(@hunk) if ($num);
  1638. my $n_lofs = 0;
  1639. my @result = ();
  1640. for (@hunk) {
  1641. if ($_->{USE}) {
  1642. push @result, @{$_->{TEXT}};
  1643. }
  1644. }
  1645. if (@result or $head->{USE}) {
  1646. my @patch = reassemble_patch($head->{TEXT}, @result);
  1647. my $apply_routine = $patch_mode_flavour{APPLY};
  1648. &$apply_routine(@patch);
  1649. refresh();
  1650. }
  1651. print "\n";
  1652. return $quit;
  1653. }
  1654. sub diff_cmd {
  1655. my @mods = list_modified('index-only');
  1656. @mods = grep { !($_->{BINARY}) } @mods;
  1657. return if (!@mods);
  1658. my (@them) = list_and_choose({ PROMPT => __('Review diff'),
  1659. IMMEDIATE => 1,
  1660. HEADER => $status_head, },
  1661. @mods);
  1662. return if (!@them);
  1663. my $reference = (is_initial_commit()) ? get_empty_tree() : 'HEAD';
  1664. system(qw(git diff -p --cached), $reference, '--',
  1665. map { $_->{VALUE} } @them);
  1666. }
  1667. sub quit_cmd {
  1668. print __("Bye.\n");
  1669. exit(0);
  1670. }
  1671. sub help_cmd {
  1672. # TRANSLATORS: please do not translate the command names
  1673. # 'status', 'update', 'revert', etc.
  1674. print colored $help_color, __ <<'EOF' ;
  1675. status - show paths with changes
  1676. update - add working tree state to the staged set of changes
  1677. revert - revert staged set of changes back to the HEAD version
  1678. patch - pick hunks and update selectively
  1679. diff - view diff between HEAD and index
  1680. add untracked - add contents of untracked files to the staged set of changes
  1681. EOF
  1682. }
  1683. sub process_args {
  1684. return unless @ARGV;
  1685. my $arg = shift @ARGV;
  1686. if ($arg =~ /--patch(?:=(.*))?/) {
  1687. if (defined $1) {
  1688. if ($1 eq 'reset') {
  1689. $patch_mode = 'reset_head';
  1690. $patch_mode_revision = 'HEAD';
  1691. $arg = shift @ARGV or die __("missing --");
  1692. if ($arg ne '--') {
  1693. $patch_mode_revision = $arg;
  1694. $patch_mode = ($arg eq 'HEAD' ?
  1695. 'reset_head' : 'reset_nothead');
  1696. $arg = shift @ARGV or die __("missing --");
  1697. }
  1698. } elsif ($1 eq 'checkout') {
  1699. $arg = shift @ARGV or die __("missing --");
  1700. if ($arg eq '--') {
  1701. $patch_mode = 'checkout_index';
  1702. } else {
  1703. $patch_mode_revision = $arg;
  1704. $patch_mode = ($arg eq 'HEAD' ?
  1705. 'checkout_head' : 'checkout_nothead');
  1706. $arg = shift @ARGV or die __("missing --");
  1707. }
  1708. } elsif ($1 eq 'worktree') {
  1709. $arg = shift @ARGV or die __("missing --");
  1710. if ($arg eq '--') {
  1711. $patch_mode = 'checkout_index';
  1712. } else {
  1713. $patch_mode_revision = $arg;
  1714. $patch_mode = ($arg eq 'HEAD' ?
  1715. 'worktree_head' : 'worktree_nothead');
  1716. $arg = shift @ARGV or die __("missing --");
  1717. }
  1718. } elsif ($1 eq 'stage' or $1 eq 'stash') {
  1719. $patch_mode = $1;
  1720. $arg = shift @ARGV or die __("missing --");
  1721. } else {
  1722. die sprintf(__("unknown --patch mode: %s"), $1);
  1723. }
  1724. } else {
  1725. $patch_mode = 'stage';
  1726. $arg = shift @ARGV or die __("missing --");
  1727. }
  1728. die sprintf(__("invalid argument %s, expecting --"),
  1729. $arg) unless $arg eq "--";
  1730. %patch_mode_flavour = %{$patch_modes{$patch_mode}};
  1731. $patch_mode_only = 1;
  1732. }
  1733. elsif ($arg ne "--") {
  1734. die sprintf(__("invalid argument %s, expecting --"), $arg);
  1735. }
  1736. }
  1737. sub main_loop {
  1738. my @cmd = ([ 'status', \&status_cmd, ],
  1739. [ 'update', \&update_cmd, ],
  1740. [ 'revert', \&revert_cmd, ],
  1741. [ 'add untracked', \&add_untracked_cmd, ],
  1742. [ 'patch', \&patch_update_cmd, ],
  1743. [ 'diff', \&diff_cmd, ],
  1744. [ 'quit', \&quit_cmd, ],
  1745. [ 'help', \&help_cmd, ],
  1746. );
  1747. while (1) {
  1748. my ($it) = list_and_choose({ PROMPT => __('What now'),
  1749. SINGLETON => 1,
  1750. LIST_FLAT => 4,
  1751. HEADER => __('*** Commands ***'),
  1752. ON_EOF => \&quit_cmd,
  1753. IMMEDIATE => 1 }, @cmd);
  1754. if ($it) {
  1755. eval {
  1756. $it->[1]->();
  1757. };
  1758. if ($@) {
  1759. print "$@";
  1760. }
  1761. }
  1762. }
  1763. }
  1764. process_args();
  1765. refresh();
  1766. if ($patch_mode_only) {
  1767. patch_update_cmd();
  1768. }
  1769. else {
  1770. status_cmd();
  1771. main_loop();
  1772. }