more subtleties
[feisty_meow.git] / scripts / rev_control / version_control.sh
1 #!/bin/bash
2
3 # these are helper functions for doing localized revision control.
4 # this script should be sourced into other scripts that use it.
5
6 source "$FEISTY_MEOW_SCRIPTS/core/launch_feisty_meow.sh"
7 source "$FEISTY_MEOW_SCRIPTS/tty/terminal_titler.sh"
8
9 #hmmm: we need to dump all the outputs in this script into splitter
10
11 ##############
12
13 # the maximum depth that the recursive functions will try to go below the starting directory.
14 export MAX_DEPTH=5
15
16 # use our splitter tool for lengthy output if it's available.
17 if [ ! -z "$(which splitter)" ]; then
18   TO_SPLITTER="$(which splitter)"
19 else
20   TO_SPLITTER=cat
21 fi
22
23 ##############
24
25 # one unpleasantry to take care of first; cygwin barfs aggressively if the TMP directory
26 # is a DOS path, but we need it to be a DOS path for our GFFS testing, so that blows.
27 # to get past this, TMP gets changed below to a hopefully generic and safe place.
28 if [[ "$TMP" =~ .:.* ]]; then
29   echo "making weirdo temporary directory for PCDOS-style path."
30   export TMP=/tmp/rev_control_$USER
31 fi
32 if [ ! -d "$TMP" ]; then
33   mkdir -p $TMP
34 fi
35 if [ ! -d "$TMP" ]; then
36   echo "could not create the temporary directory TMP in: $TMP"
37   echo "this script will not work properly without an existing TMP directory."
38 fi
39
40 ##############
41
42 # checks the directory provided into the revision control system repository it belongs to.
43 function do_checkin()
44 {
45   local directory="$1"; shift
46
47   save_terminal_title
48
49   # make a nice echoer since we want to use it inside conditions below.
50   local nicedir="$directory"
51   if [ $nicedir == "." ]; then
52     nicedir=$(\pwd)
53   fi
54   local blatt="echo checking in '$nicedir'..."
55
56   do_update "$directory"
57   test_or_die "repository update--this should be fixed before check-in."
58
59   pushd "$directory" &>/dev/null
60   if [ -f ".no-checkin" ]; then
61     echo "skipping check-in due to presence of .no-checkin sentinel file."
62   elif [ -d "CVS" ]; then
63     if test_writeable "CVS"; then
64       $blatt
65       cvs ci .
66       test_or_die "cvs checkin"
67     fi
68   elif [ -d ".svn" ]; then
69     if test_writeable ".svn"; then
70       $blatt
71       svn ci .
72       test_or_die "svn checkin"
73     fi
74   elif [ -d ".git" ]; then
75     if test_writeable ".git"; then
76       $blatt
77       # snag all new files.  not to everyone's liking.
78       git add --all .
79       test_or_die "git add all new files"
80
81       # see if there are any changes in the local repository.
82       if ! git diff-index --quiet HEAD --; then
83         # tell git about all the files and get a check-in comment.
84         git commit .
85         test_or_die "git commit"
86       fi
87       # catch if the diff-index failed somehow.
88       test_or_die "git diff-index"
89
90       # we continue on to the push, even if there were no changes this time, because
91       # there could already be committed changes that haven't been pushed yet.
92
93       local myself="$(my_branch_name)"
94       local parent="$(parent_branch_name)"
95
96       # upload any changes to the upstream repo so others can see them.
97       if [ "$myself" != "$parent" ]; then
98         git push origin "$(myself)" 2>&1 | grep -v "X11 forwarding request failed" | $TO_SPLITTER
99         test_or_die "git push to origin: $myself"
100       else
101         # this branch is the same as the parent, so just push.
102         git push 2>&1 | grep -v "X11 forwarding request failed" | $TO_SPLITTER
103         test_or_die "normal git push"
104       fi
105
106     fi
107   else
108     # nothing there.  it's not an error though.
109     echo no repository in $directory
110   fi
111   popd &>/dev/null
112
113   restore_terminal_title
114
115   true;
116 }
117
118 # shows the local changes in a repository.
119 function do_diff
120 {
121   local directory="$1"; shift
122
123   save_terminal_title
124
125   pushd "$directory" &>/dev/null
126
127   # only update if we see a repository living there.
128   if [ -d ".svn" ]; then
129     svn diff .
130     test_or_die "subversion diff"
131   elif [ -d ".git" ]; then
132     git diff 
133     test_or_die "git diff"
134   elif [ -d "CVS" ]; then
135     cvs diff .
136     test_or_die "cvs diff"
137   fi
138
139   popd &>/dev/null
140
141   restore_terminal_title
142
143   true;
144 }
145
146 # reports any files that are not already known to the upstream repository.
147 function do_report_new
148 {
149   local directory="$1"; shift
150
151   save_terminal_title
152
153   pushd "$directory" &>/dev/null
154
155   # only update if we see a repository living there.
156   if [ -f ".no-checkin" ]; then
157     echo "skipping reporting due to presence of .no-checkin sentinel file."
158   elif [ -d ".svn" ]; then
159     # this action so far only makes sense and is needed for svn.
160     bash $FEISTY_MEOW_SCRIPTS/rev_control/svnapply.sh \? echo
161     test_or_die "svn diff"
162   elif [ -d ".git" ]; then
163     git status -u
164     test_or_die "git status -u"
165   fi
166
167   popd &>/dev/null
168
169   restore_terminal_title
170
171   true
172 }
173
174 # checks in all the folders in a specified list.
175 function checkin_list()
176 {
177   # make the list of directories unique.
178   local list="$(uniquify $*)"
179
180   save_terminal_title
181
182   # turn repo list back into an array.
183   eval "repository_list=( ${REPOSITORY_LIST[*]} )"
184
185   local outer inner
186
187   for outer in "${repository_list[@]}"; do
188     # check the repository first, since it might be an absolute path.
189     if [[ $outer =~ /.* ]]; then
190       # yep, this path is absolute.  just handle it directly.
191       if [ ! -d "$outer" ]; then continue; fi
192       do_checkin $outer
193       test_or_die "running check-in (absolute) on path: $outer"
194       sep 28
195     else
196       for inner in $list; do
197         # add in the directory component to see if we can find the folder.
198         local path="$inner/$outer"
199         if [ ! -d "$path" ]; then continue; fi
200         do_checkin $path
201         test_or_die "running check-in (relative) on path: $path"
202         sep 28
203       done
204     fi
205   done
206
207   restore_terminal_title
208 }
209
210 # takes out the first few carriage returns that are in the input.
211 function squash_first_few_crs()
212 {
213   i=0
214   while read input_text; do
215     i=$((i+1))
216     if [ $i -le 5 ]; then
217       echo -n "$input_text  "
218     else
219       echo $input_text
220     fi
221   done
222   if [ $i -le 3 ]; then
223     # if we're still squashing eols, make sure we don't leave them hanging.
224     echo
225   fi
226 }
227
228 # a helpful method that reports the git branch for the current directory's
229 # git repository.
230 function my_branch_name()
231 {
232   echo "$(git branch | grep \* | cut -d ' ' -f2)"
233 }
234
235 # this reports the upstream branch for the current repo.
236 function parent_branch_name()
237 {
238   echo "$(git branch -vv | grep \* | cut -d ' ' -f2)"
239 }
240
241 # gets the latest versions of the assets from the upstream repository.
242 function do_update()
243 {
244   directory="$1"; shift
245
246   save_terminal_title
247
248   # make a nice echoer since we want to use it inside conditions below.
249   local nicedir="$directory"
250   if [ $nicedir == "." ]; then
251     nicedir=$(\pwd)
252   fi
253   local blatt="echo retrieving '$nicedir'..."
254
255   pushd "$directory" &>/dev/null
256   if [ -d "CVS" ]; then
257     if test_writeable "CVS"; then
258       $blatt
259       cvs update . | $TO_SPLITTER
260       test_or_die "cvs update"
261     fi
262   elif [ -d ".svn" ]; then
263     if test_writeable ".svn"; then
264       $blatt
265       svn update . | $TO_SPLITTER
266       test_or_die "svn update"
267     fi
268   elif [ -d ".git" ]; then
269     if test_writeable ".git"; then
270       $blatt
271
272       # from very helpful page:
273       # https://stackoverflow.com/questions/10312521/how-to-fetch-all-git-branches
274       for remote in $( git branch -r | grep -v -- '->' ); do
275         git branch --track ${remote#origin/} $remote 2>/dev/null
276 #hmmm: ignoring errors from these, since they are continual.
277 #hmmm: if we could find a way to not try to track with a local branch when there's already one present, that would be swell.  it's probably simple.
278       done
279
280 #      git fetch --all 2>&1 | grep -v "X11 forwarding request failed" | $TO_SPLITTER
281 #      test_or_die "git fetch"
282
283       git pull --all 2>&1 | grep -v "X11 forwarding request failed" | $TO_SPLITTER
284       test_or_die "git pull"
285     fi
286   else
287     # this is not an error necessarily; we'll just pretend they planned this.
288     echo no repository in $directory
289   fi
290   popd &>/dev/null
291
292   restore_terminal_title
293
294   true
295 }
296
297 # gets all the updates for a list of folders under revision control.
298 function checkout_list()
299 {
300   local list="$(uniquify $*)"
301
302   save_terminal_title
303
304   # turn repo list back into an array.
305   eval "repository_list=( ${REPOSITORY_LIST[*]} )"
306
307   local outer inner
308
309   for outer in "${repository_list[@]}"; do
310     # check the repository first, since it might be an absolute path.
311     if [[ $outer =~ /.* ]]; then
312       # yep, this path is absolute.  just handle it directly.
313       if [ ! -d "$outer" ]; then continue; fi
314       do_update $outer
315       test_or_die "running update on: $path"
316       sep 28
317     else
318       for inner in $list; do
319         # add in the directory component to see if we can find the folder.
320         local path="$inner/$outer"
321         if [ ! -d "$path" ]; then continue; fi
322         do_update $path
323         test_or_die "running update on: $path"
324         sep 28
325       done
326     fi
327   done
328
329   restore_terminal_title
330 }
331
332 # provides a list of absolute paths of revision control directories
333 # that are located under the directory passed as the first parameter.
334 function generate_rev_ctrl_filelist()
335 {
336   local dir="$1"; shift
337   pushd "$dir" &>/dev/null
338   local dirhere="$( \cd "$(\dirname "$dir")" && /bin/pwd )"
339   local tempfile=$(mktemp /tmp/zz_checkins.XXXXXX)
340   echo >$tempfile
341   local additional_filter
342   find $dirhere -follow -maxdepth $MAX_DEPTH -type d -iname ".svn" -exec echo {}/.. ';' >>$tempfile 2>/dev/null
343   find $dirhere -follow -maxdepth $MAX_DEPTH -type d -iname ".git" -exec echo {}/.. ';' >>$tempfile 2>/dev/null
344   # CVS is not well behaved like git and (now) svn, and we seldom use it anymore.
345   popd &>/dev/null
346
347   # see if they've warned us not to try checking in within vendor hierarchies.
348   if [ ! -z "NO_CHECKIN_VENDOR" ]; then
349     sed -i -e '/.*\/vendor\/.*/d' "$tempfile"
350   fi
351
352   local sortfile=$(mktemp /tmp/zz_checkin_sort.XXXXXX)
353   sort <"$tempfile" >"$sortfile"
354   \rm "$tempfile"
355   echo "$sortfile"
356 }
357
358 # iterates across a list of directories contained in a file (first parameter).
359 # on each directory name, it performs the action (second parameter) provided.
360 function perform_revctrl_action_on_file()
361 {
362   local tempfile="$1"; shift
363   local action="$1"; shift
364
365   save_terminal_title
366
367   while read -u 3 dirname; do
368     if [ -z "$dirname" ]; then continue; fi
369     pushd "$dirname" &>/dev/null
370     echo "[$(pwd)]"
371     $action .
372     test_or_die "performing action $action on: $(pwd)"
373     sep 28
374     popd &>/dev/null
375   done 3<"$tempfile"
376
377   restore_terminal_title
378
379   rm $tempfile
380 }
381