3 ## git-wt-add: A darcs-style interactive staging script for git. As the
4 ## name implies, git-wt-add walks you through unstaged changes on a
5 ## hunk-by-hunk basis and allows you to pick the ones you'd like staged.
7 ## git-wt-add Copyright 2007 William Morgan <wmorgan-git-wt-add@masanjin.net>.
8 ## This program is free software: you can redistribute it and/or modify
9 ## it under the terms of the GNU General Public License as published by
10 ## the Free Software Foundation, either version 3 of the License, or
11 ## (at your option) any later version.
13 ## This program is distributed in the hope that it will be useful,
14 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ## GNU General Public License for more details.
18 ## You can find the GNU General Public License at:
19 ## http://www.gnu.org/licenses/
24 attr_reader
:file, :file_header, :diff
25 attr_accessor
:disposition
27 def initialize file
, file_header
, diff
29 @file_header = file_header
31 @disposition = :unknown
34 def self.make_from diff
37 file_header
= hunk
= file
= nil
39 diff
.each
do |l
| # a little state machine to parse git diff output
44 when state
== :outside && l
=~
/^(#{COLOR})*diff --git a\/(.+) b\
/(\2)/
47 when state
== :outside && l
=~
/^(#{COLOR})*index /
48 when state
== :outside && l
=~
/^(#{COLOR})*(---|\+\+\+) /
49 file_header
+= l
+ "\n"
50 when state
== :outside && l
=~
/^(#{COLOR})*@@ /
53 when state
== :in_hunk && l
=~
/^(#{COLOR})*(@@ |diff --git a)/
54 ret
<< Hunk
.new(file
, file_header
, hunk
)
57 when state
== :in_hunk
60 raise "unparsable diff input: #{l.inspect}"
66 ret
<< Hunk
.new(file
, file_header
, hunk
) if hunk
76 w: wait and decide later, defaulting to no
78 s: don't record the rest of the changes to this file
79 f: record the rest of the changes to this file
81 d: record selected patches, skipping all the remaining patches
82 a: record all the remaining patches
86 k: back up to previous patch
87 c: calculate number of patches
88 h or ?: show this help
90 <Space>: accept the current default (which is capitalized)
94 def walk_through hunks
95 skip_files
, record_files
= {}, {}
96 skip_rest
= record_rest
= false
98 while hunks
.any
? { |h
| h
.disposition
== :unknown }
100 until pos
>= hunks
.length
102 if h
.disposition
!= :unknown
105 elsif skip_rest
|| skip_files
[h
.file
]
106 h
.disposition
= :ignore
109 elsif record_rest
|| record_files
[h
.file
]
110 h
.disposition
= :record
115 puts
"Hunk from #{h.file}"
117 print
"Shall I stage this change? (#{pos + 1}/#{hunks.length}) [ynWsfqadk], or ? for help: "
121 when ?y
: h
.disposition
= :record
122 when ?n
: h
.disposition
= :ignore
123 when ?w
, ?\
: h
.disposition
= :unknown
125 h
.disposition
= :ignore
126 skip_files
[h
.file
] = true
128 h
.disposition
= :record
129 record_files
[h
.file
] = true
130 when ?d
: skip_rest
= true
131 when ?a
: record_rest
= true
135 hunks
[pos
- 1].disposition
= :unknown
136 pos
-= 2 # double-bah
153 next unless h
.disposition
== :record
154 unless did_header
[h
.file
]
155 patch
+= h
.file_header
156 did_header
[h
.file
] = true
164 ### execution starts here ###
166 diff
= `git diff`.split(/\r?\n/)
168 puts
"No unstaged changes."
171 hunks
= Hunk
.make_from diff
176 `stty -icanon` # immediate keypress mode
182 patch
= make_patch hunks
184 puts
"No changes selected for staging."
186 IO
.popen("git apply --cached", "w") { |f
| f
.puts patch
}
188 Staged patch of #{patch.split("\n").size} lines.
190 Possible next commands:
191 git diff --cached: see staged changes
192 git commit: commit staged changes
193 git reset: unstage changes