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