c87eee51b5442e46057271f4f1d7a97aa7f4594e
[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 # this exits with 0 for success (normal bash behavior) when up to date.  if the branch is not up to date,
255 # then these values are returned:
256 #DOCUMENT THE VALUES
257 # reference: https://stackoverflow.com/questions/3258243/check-if-pull-needed-in-git
258 function check_branch_state()
259 {
260   local branch="$1"; shift
261
262   local to_return=120  # unknown issue.
263
264   LOCAL=$(git rev-parse @)
265   REMOTE=$(git rev-parse "$branch")
266   BASE=$(git merge-base @ "$branch")
267 var branch LOCAL REMOTE BASE
268
269   if [ "$LOCAL" == "$REMOTE" ]; then
270     echo "Up-to-date"
271     to_return=0
272   elif [ "$LOCAL" == "$BASE" ]; then
273     echo "Need to pull"
274     to_return=1
275   elif [ "$REMOTE" == "$BASE" ]; then
276     echo "Need to push"
277     to_return=2
278   else
279     echo "Diverged"
280     to_return=3
281   fi
282
283   return $to_return
284 }
285
286 # the git update process just gets more and more complex when you bring in
287 # branches, so we've moved this here to avoid having a ton of code in the
288 # do_checkin method.
289 function careful_git_update()
290 {
291   # first update all our remote branches to their current state from the repos.
292   git remote update
293   test_or_die "git remote update"
294
295 #is parent branch the right thing to tell it?
296 #or we want mybranch for real, don't we?
297 #  check_branch_state $(parent_branch_name)
298   state=$(check_branch_state $(my_branch_name) )
299 test_or_continue "branch state check"
300 #need to instead do something here if fails.
301
302   # now pull down any changes in our own origin in the repo, to stay in synch
303   # with any changes from others.
304   git pull --no-ff --all
305   test_or_die "git pulling all upstream"
306
307
308 echo The rest of pull is not being done yet.
309 return 1
310
311
312
313 # below has older shards of partial knowledge.
314
315 #      reslog=$(git log HEAD..origin/master --oneline)
316 #      if [[ "${reslog}" != "" ]] ; then
317 #        git merge origin/master
318
319 #      # from very helpful page:
320 #      # https://stackoverflow.com/questions/10312521/how-to-fetch-all-git-branches
321 #      for remote in $( git branch -r | grep -v -- '->' ); do
322 #        git branch --track ${remote#origin/} $remote 2>/dev/null
323 ##hmmm: ignoring errors from these, since they are continual.
324 ##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.
325 #      done
326 #
327 ##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.
328 #      git fetch --all 2>&1 | grep -v "X11 forwarding request failed" | $TO_SPLITTER
329 #      test_or_die "git fetch"
330 #
331 #      git pull --all 2>&1 | grep -v "X11 forwarding request failed" | $TO_SPLITTER
332 #      test_or_die "git pull"
333
334 }
335
336 # gets the latest versions of the assets from the upstream repository.
337 function do_update()
338 {
339   directory="$1"; shift
340
341   save_terminal_title
342
343   # make a nice echoer since we want to use it inside conditions below.
344   local nicedir="$directory"
345   if [ $nicedir == "." ]; then
346     nicedir=$(\pwd)
347   fi
348   local blatt="echo retrieving '$nicedir'..."
349
350   pushd "$directory" &>/dev/null
351   if [ -d "CVS" ]; then
352     if test_writeable "CVS"; then
353       $blatt
354       cvs update . | $TO_SPLITTER
355       test_or_die "cvs update"
356     fi
357   elif [ -d ".svn" ]; then
358     if test_writeable ".svn"; then
359       $blatt
360       svn update . | $TO_SPLITTER
361       test_or_die "svn update"
362     fi
363   elif [ -d ".git" ]; then
364     if test_writeable ".git"; then
365       $blatt
366
367 # classic implementation, but only works with one master branch.
368 # fixes will be forthcoming from development branch.
369
370 #      git pull 2>&1 | grep -v "X11 forwarding request failed" | $TO_SPLITTER
371 #      if [ ${PIPESTATUS[0]} -ne 0 ]; then false; fi
372 #      test_or_die "git pull"
373
374 #any parms needed?
375       careful_git_update 
376
377     fi
378   else
379     # this is not an error necessarily; we'll just pretend they planned this.
380     echo no repository in $directory
381   fi
382   popd &>/dev/null
383
384   restore_terminal_title
385
386   true
387 }
388
389 # gets all the updates for a list of folders under revision control.
390 function checkout_list()
391 {
392   local list="$(uniquify $*)"
393
394   save_terminal_title
395
396   # turn repo list back into an array.
397   eval "repository_list=( ${REPOSITORY_LIST[*]} )"
398
399   local outer inner
400
401   for outer in "${repository_list[@]}"; do
402     # check the repository first, since it might be an absolute path.
403     if [[ $outer =~ /.* ]]; then
404       # yep, this path is absolute.  just handle it directly.
405       if [ ! -d "$outer" ]; then continue; fi
406       do_update $outer
407       test_or_die "running update on: $path"
408       sep 28
409     else
410       for inner in $list; do
411         # add in the directory component to see if we can find the folder.
412         local path="$inner/$outer"
413         if [ ! -d "$path" ]; then continue; fi
414         do_update $path
415         test_or_die "running update on: $path"
416         sep 28
417       done
418     fi
419   done
420
421   restore_terminal_title
422 }
423
424 # provides a list of absolute paths of revision control directories
425 # that are located under the directory passed as the first parameter.
426 function generate_rev_ctrl_filelist()
427 {
428   local dir="$1"; shift
429   pushd "$dir" &>/dev/null
430   local dirhere="$( \cd "$(\dirname "$dir")" && /bin/pwd )"
431   local tempfile=$(mktemp /tmp/zz_checkins.XXXXXX)
432   echo >$tempfile
433   local additional_filter
434   find $dirhere -follow -maxdepth $MAX_DEPTH -type d -iname ".svn" -exec echo {}/.. ';' >>$tempfile 2>/dev/null
435   find $dirhere -follow -maxdepth $MAX_DEPTH -type d -iname ".git" -exec echo {}/.. ';' >>$tempfile 2>/dev/null
436   # CVS is not well behaved like git and (now) svn, and we seldom use it anymore.
437   popd &>/dev/null
438
439   # see if they've warned us not to try checking in within vendor hierarchies.
440   if [ ! -z "NO_CHECKIN_VENDOR" ]; then
441     sed -i -e '/.*\/vendor\/.*/d' "$tempfile"
442   fi
443
444   local sortfile=$(mktemp /tmp/zz_checkin_sort.XXXXXX)
445   sort <"$tempfile" >"$sortfile"
446   \rm "$tempfile"
447   echo "$sortfile"
448 }
449
450 # iterates across a list of directories contained in a file (first parameter).
451 # on each directory name, it performs the action (second parameter) provided.
452 function perform_revctrl_action_on_file()
453 {
454   local tempfile="$1"; shift
455   local action="$1"; shift
456
457   save_terminal_title
458
459   while read -u 3 dirname; do
460     if [ -z "$dirname" ]; then continue; fi
461     pushd "$dirname" &>/dev/null
462     echo "[$(pwd)]"
463     $action .
464     test_or_die "performing action $action on: $(pwd)"
465     sep 28
466     popd &>/dev/null
467   done 3<"$tempfile"
468
469   restore_terminal_title
470
471   rm $tempfile
472 }
473