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
|
#!/bin/sh
#
# git-deli.sh: delinearized git workflows helper
#
# Copytight (C) 2025 Mirek Kratochvil <exa.exa@gmail.com>
#
OPTS_SPEC="\
git deli head [-b=<branch>] [-m=<msg>] [<commit>]
git deli commit ...
git deli boundary [-d] <commit>
git deli merge <commit> ...
git deli push [-f] <repository> <refspec>
git deli pull <repository> <ref>
--
h,help! show the help
m,message!= specify the commit message
'head' options:
b,branch!= start a new branch instead of using the target one
'boundary' options:
d,delete delete the boundary commit instead of adding it
'push' options:
f,force internally do a true force-push instead of the safe force-with-lease
"
main() {
if test $# -eq 0
then set -- -h
fi
set_args="$(echo "$OPTS_SPEC" | git-rev-parse --parseopt --stuck-long -- "$@" || echo exit $?)"
eval "$set_args"
. git-sh-setup
require_work_tree
# find the command right away (TODO: actually validate if the options
# belong to the given command later)
while test $# -gt 0
do
opt="$1"
shift
[ "$opt" = "--" ] && break
done
arg_command=$1
eval "$set_args"
arg_msg=
arg_branch=
arg_all=
arg_patch=
arg_delete=
arg_force=
while test $# -gt 0
do
opt="$1"
shift
case "$opt" in
--message=*)
[ -z "$arg_msg" ] || die "conflicting options for --message"]
arg_msg="${opt#*=}" ;;
--branch=*)
[ -z "$arg_branch" ] || die "conflicting options for --branch"]
arg_branch="${opt#*=}" ;;
--delete)
[ -z "$arg_delete" ] || die "conflicting options for --delete"]
arg_delete=yes ;;
--no-delete)
[ -z "$arg_delete" ] || die "conflicting options for --delete"]
arg_delete=no ;;
--force)
[ -z "$arg_force" ] || die "conflicting options for --force"]
arg_force=yes ;;
--no-force)
[ -z "$arg_force" ] || die "conflicting options for --force"]
arg_force=no ;;
--)
break ;;
*)
die "fatal: unexpected option: $opt"
esac
done
shift # drop the command, we already got it
case "$arg_command" in
head) cmd_head "$arg_msg" "$arg_branch" "$@" ;;
commit) cmd_commit "$arg_msg" "$@" ;;
boundary) cmd_boundary "$arg_delete" "$@" ;;
merge) cmd_merge "$@" ;;
push) cmd_push "$arg_force" "$@" ;;
pull) cmd_pull "$@" ;;
*)
die "fatal: unknonwn command: $arg_command" ;;
esac
}
cmd_head () {
msg="$1"
branch="$2"
# TODO one extra optional commit arg
# if the arg is there, this is "rebase" mode that picks up a deli view
# from the history and separates the commits into branches below it
#
# if it's not there, we make a new head on the current branch, possibly
# making a new one in the process
}
cmd_commit () {
msg="$1"
shift
# TODO run a git commit with all extra args, THEN run head with the original commit
}
cmd_boundary () {
delete="$1"
shift
[ $# -eq 1 ] || die "specify one boundary commit"
commit="$1"
# TODO this adds or removes the "extra parents" to the head commit
}
cmd_merge () {
# TODO this should merge multiple "head" commits
# notably, there must not be any actual merging involved -- there must
# be no conflicts etc. What happens is that, quite simply, the parents
# of the new head become all (latest) parents of all original heads. If
# there is any actual merging required, it must be resolved elsewhere
# and recorded in the history so that branches merge cleanly.
}
cmd_push () {
# TODO like git-push but actually force-pushes the current multihead.
# Uses a force-with-lease by default.
}
cmd_pull () {
# TODO like a normal git fetch followed by git deli merge
}
main "$@"
|