summaryrefslogtreecommitdiff
path: root/lib-src/mr/stow
blob: 254bd0c33ac494dfa4eef8d96af55c1c469702c0 (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
# Plug-in to use GNU Stow to manage symlinks whose targets lie in a
# repository managed with myrepos
#
# The standard use case is for managing dotfiles inside one's home
# directory.
#
# Original author (2011):
# Adam Spiers <mr@adamspiers.org>
#
# This version reworked (2016, 2017) & maintained (2017) 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', 'unstow' and 'adopt' 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.  This will cause stow operations to fail.
#
# To deal with this, run 'mr adopt'.  This will move the modified file
# into your repository, and restore the usual symlink.  Then you can
# use your VCS tools ('git diff', 'hg diff') to decide whether you
# want to keep the changes.
#
# FOLDING
#
# By default, this library passes --no-folding to stow.  This allows
# you to have more than one repository stowing files into a single
# subdirectory in your home directory.  For example, you might have a
# private and a public repository both stowing into ~/.gnupg.  If you
# don't want this behaviour, set MR_FOLD.  For example, in a
# repository's myrepos config section or in [DEFAULT]:
#
#    lib = MR_FOLD=
#
# 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"
        # canonicalise -t and -d params with readlink if available
        # stow can fail if they aren't canonical
        if which readlink >/dev/null 2>&1; then
            stow_common_opts="-t $(readlink -f $STOW_TARGET) -d $(readlink -f $STOW_DIR)"
        else
            stow_common_opts="-t $STOW_TARGET -d $STOW_DIR"
        fi
        STOW="${STOW_COMMAND:-$HOME/src/dotfiles/lib-src/stow/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 ! (: "${MR_FOLD?}") 2>/dev/null; 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
        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"
    }
    #
    mr_adopt () {
        stowable_then_continue || return 0
        set_stow_common_opts
        ensure_package_exists
        mr_pre_unstow
        command "$STOW" --adopt $stow_common_opts "$@" "$STOW_NAME"
        mr_post_stow
        info "Stowed $STOW_NAME with adoption"
    }
    #
    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
    }

#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 "$@"

# Local variables:
# mode: sh
# End: