summaryrefslogtreecommitdiff
path: root/lib/mr/stow
blob: c1d72877249bc759fbf9dd933f619910c0965088 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# Plug-in to use GNU Stow to manage symlinks whose targets lie in a
# repository managed with myrepos
#
# Original author:
# Adam Spiers <mr@adamspiers.org>
#
# This version reworked & maintained by:
# Sean Whitton <spwhitton@spwhitton.name>

# BASIC USAGE INSTRUCTIONS
#
# To make mr use this file, add a line like this inside the [DEFAULT]
# section of your ~/.mrconfig:
#
#   include = cat /usr/share/mr/stow
#
# and then inside each [repo] section of your ~/.mrconfig for
# which you want the contents to be stowed, add this line:
#
#   stowable = true
#
# You must have at least version 2.1.0 of stow available.  [1]
#
# If stow is not in your $PATH, you can export STOW_COMMAND to tell
# this plug-in where it is.
#
# The default behaviour is to stow on checkout, and restow on update.
# The manual actions 'stow', 'restow', and 'unstow' are also
# available.
#
# By default, ~/.STOW is used as the stow directory, and ~ as the
# target directory.  You can export STOW_DIR and STOW_TARGET to
# override these defaults.
#
# DEALING WITH APPLICATIONS THAT MISTREAT SYMLINKS
#
# Some programs will replace a symlink to a stowed file with a regular
# copy of the file, and a subset of these will do this even if they
# haven't edited the file.  There are two actions designed to deal
# with this:
#
# - 'cd ~/stowed && mr adopt ~/foo' will replace ~/stowed/foo with
#   ~/foo so that when you restow, ~/foo is once again a symlink
#   pointing to ~/stowed/foo
#
# - 'cd ~/stowed && mr misstowed' will output a list of all symlinks
#   that have been replaced with regular files *and modified* by you
#   or a program.
#
# - If you pass '--delete-unmodified' to 'mr misstowed', it will
#   additionally delete all symlink targets that have been replaced
#   with *unmodified* regular files
#
# The recommended workflow is:
#
# 1. Run 'mr misstowed', look for files whose modifications you want
#    to keep, and run 'mr adopt' on them.
#
# 2. Run 'mr restowover' to replace unmodified regular files, or
#    regular files whose modifications you want to discard, with
#    symlinks once again.
#
# FIXUPS THAT CREATE FILES TO BE STOWED
#
# Stowing is automatically performed via post_checkout, and restowing
# via post_update, as can be seen from below (search for 'Automatic
# actions').  Note that these run before fixups, which allows fixups
# to refer to stowed files, but isn't ideal if the fixups are
# responsible for creating the stow package's installation image,
# e.g. via a typical './configure && make install' sequence.  Here's a
# suggested mrconfig chunk to handle this particular use case:
#
#     stowable = true
#     lib =
#         STOW_PKG_TYPE=directory
#         STOW_NO_AUTOMATIC_ACTIONS=yes
#         mr_pre_unstow () {
#             install-info --delete --info-dir=$HOME/share/info $STOW_PKG_PATH/share/info/*.info
#         }
#         mr_post_stow () {
#             install-info --info-dir=$HOME/share/info $STOW_PKG_PATH/share/info/*.info
#         }
#     fixups =
#         if ! [ -e configure ]; then
#             bash ./autogen.sh
#         fi
#         set_stow_common_opts
#         ./configure --prefix=$STOW_PKG_PATH
#         make install prefix=$STOW_PKG_PATH
#         rm $STOW_PKG_PATH/share/info/dir
#         mr_restow_regardless
#
# [1] Older versions could create a frankenstein ~/.git/ directory
# containing symlinks to multiple .git/ sub-directories in different
# stow packages!  2.1.0 onwards does not have this problem - it
# supports local per-directory .stow-local-ignore and global
# ~/.stow-global-ignore files, and even without configuration of
# these, it chooses sensible default ignore lists which prevent
# stowing of a package's .git/ sub-directory.  These ignore lists are
# also ideal if you only want to stow a subset of a stow package's
# contents.

lib =
    : ${STOW_DIR:=$HOME/.STOW}
    : ${STOW_TARGET:=$HOME}
    STOW_NAME=$(echo "$MR_REPO" | tr / _)
    #
    if ! [ -d "$STOW_TARGET"    ]; then mkdir -p "$STOW_TARGET"; fi
    if ! [ -d "$STOW_DIR"       ]; then mkdir -p "$STOW_DIR"   ; fi
    if ! [ -f "$STOW_DIR/.stow" ]; then touch "$STOW_DIR/.stow"; fi
    #
    #MR_STOWABLE=no
    is_stowable () {
        [ -z "$MR_DISABLE_STOW" ] &&
        ( cd "$MR_REPO" && mr stowable >/dev/null 2>&1 )
        #[ "$MR_STOWABLE" = yes ]
    }
    stowable_then_continue () {
        if is_stowable; then
            return 0
        else
            if [ -n "$1" ]; then
                info "$STOW_NAME isn't stowable; skipping $MR_ACTION"
            fi
            return 1
        fi
    }
    #
    set_stow_common_opts () {
        : ${STOW_PKG_TYPE:=symlink}
        STOW_PKG_PATH="$STOW_DIR/$STOW_NAME"
        stow_common_opts="-t $STOW_TARGET -d $STOW_DIR"
        STOW="${STOW_COMMAND:-stow}"
        case "`$STOW --version`" in
            'version 1.*')
                stow_common_opts="$stow_common_opts -p"
                ;;
            *)
                ;;
        esac
        if [ -n "$MR_STOW_OPTIONS" ]; then
            stow_common_opts="$stow_common_opts $MR_STOW_OPTIONS"
        fi
        if [ -n "$MR_STOW_OVER" ]; then
            stow_common_opts="$stow_common_opts --override=$MR_STOW_OVER"
        fi
        if [ -z "$MR_FOLD" ]; then
            stow_common_opts="$stow_common_opts --no-folding"
        fi
    }
    #
    mr_stow () {
        stowable_then_continue || return 0
        set_stow_common_opts
        ensure_package_exists
        command "$STOW" $stow_common_opts "$@" "$STOW_NAME"
        mr_post_stow
        info "Stowed $STOW_NAME"
    }
    mr_restow_if_already_stowed () {
        stowable_then_continue || return 0
        if ! [ -L "$STOW_PKG_PATH" ]; then
            info "$MR_REPO wasn't stowed yet; won't restow."
            return
        fi
        mr_restow_regardless "$@"
    }
    mr_restow_regardless () {
        stowable_then_continue || return 0
        set_stow_common_opts
        ensure_package_exists
        mr_pre_unstow
        # don't bother the user about unmodified dereferenced
        # symlinks; just fix them:
        mr_misstowed --delete-unmodified >/dev/null
        command "$STOW" -R $stow_common_opts "$@" "$STOW_NAME"
        mr_post_stow
        info "Restowed $STOW_NAME"
    }
    mr_pre_unstow () {
        : # This can be "overridden" by the lib section of a repo definition
        #info "no mr_pre_unstow hook"
    }
    mr_post_stow () {
        : # This can be "overridden" by the lib section of a repo definition
        #info "no mr_post_stow hook"
    }
    mr_unstow () {
        stowable_then_continue || return 0
        set_stow_common_opts
        if ! [ -d "$STOW_PKG_PATH" ]; then
            info "$MR_REPO wasn't stowed yet in $STOW_PKG_PATH; can't unstow."
            return
        fi
        mr_pre_unstow
        command "$STOW" -D $stow_common_opts "$@" "$STOW_NAME"
        if [ "$STOW_PKG_TYPE" = 'symlink' ]; then
            rm -f "$STOW_PKG_PATH"
        fi
        info "Unstowed $STOW_NAME"
    }
    #
    ensure_symlink_exists () {
        [ $# = 2 ] || error "CONFIG BUG: Usage: ensure_symlink_exists SYMLINK TARGET"
        symlink="$1"
        required_target="$2"
        if [ -L "$symlink" ]; then
            actual_target="`readlink $symlink`"
            if [ "$actual_target" = "$required_target" ]; then
                return
            else
                error "Symlink $symlink already points to $actual_target, cannot point to $required_target; aborting."
            fi
        fi
        if [ -e "$symlink" ]; then
            error "Cannot create symlink $symlink - already exists; aborting."
        fi
        ln -s "$required_target" "$symlink"
    }
    #
    ensure_package_exists () {
        case "$STOW_PKG_TYPE" in
            symlink)
                ensure_symlink_exists "$STOW_PKG_PATH" "$MR_REPO"
                ;;
            directory)
                [ -e "$STOW_PKG_PATH" ] || mkdir "$STOW_PKG_PATH"
                [ -d "$STOW_PKG_PATH" ] || error "Expected $STOW_PKG_PATH to be a directory; aborting."
                if [ -L "$STOW_PKG_PATH" ]; then
                    error "Didn't expect $STOW_PKG_PATH to be a symlink; aborting."
                fi
                ;;
            *)
                error "Unrecognised value '$STOW_PKG_TYPE' for \$STOW_PKG_TYPE; aborting."
                ;;
        esac
    }
    #
    mr_adopt () {
        stowable_then_continue || return 0
        for wanted in "$@"; do
            if [ -e "$wanted" -a ! -L "$wanted" ]; then
                dest=$(printf '%s' "$wanted" | sed -e "s@$STOW_TARGET@$MR_REPO@")
                mkdir -p "$(dirname $dest)"
                mv "$wanted" "$dest"
            else
                error "$wanted is a symlink; won't adopt"
            fi
        done
    }
    #
    mr_misstowed () {
        stowable_then_continue || return 0
        re="^  \* existing target is neither a link nor a directory: "
        for file in $(mr_restow_regardless 3>&1 1>&2 2>&3 | grep "$re" \
                          | sed -e "s|$re|$STOW_TARGET/|" | sort | uniq); do
            dest=$(printf '%s' "$file" | sed -e "s@$STOW_TARGET@$MR_REPO@")
            # this 'if' is executed if the file IS different from the repository version
            if ! diff -q "$dest" "$file" >/dev/null 2>&1; then
                echo "$file"
            elif [ "$1" = "--delete-unmodified" ]; then
                rm "$file"
            fi
        done
    }

#stowable      = is_stowable
stowable      = false
showstowable  =
    if is_stowable; then
        echo "$STOW_NAME is stowable"
    else
        echo "$STOW_NAME is not stowable"
    fi

# Automatic actions
post_checkout_append = [ -n "$STOW_NO_AUTOMATIC_ACTIONS" ] || mr_stow
#post_update_append   = mr_restow_if_already_stowed
post_update_append   = [ -n "$STOW_NO_AUTOMATIC_ACTIONS" ] || mr_restow_regardless

# Manual actions
stow          = mr_stow "$@"
stowover      = MR_STOW_OVER=. mr_stow "$@"
unstow        = mr_unstow "$@"
restow        = mr_restow_regardless "$@"
restowover    = MR_STOW_OVER=. mr_restow_regardless "$@"
adopt         = mr_adopt "$@"
misstowed     = mr_misstowed "$@"

# Local variables:
# mode: sh
# End: