diff options
| -rwxr-xr-x | bin/git-deli | 55 | ||||
| -rwxr-xr-x | git-deli | 64 |
2 files changed, 91 insertions, 28 deletions
diff --git a/bin/git-deli b/bin/git-deli index 6cca2cc..ca45002 100755 --- a/bin/git-deli +++ b/bin/git-deli @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # # git-deli.sh: delinearized git workflows helper # @@ -11,17 +11,17 @@ OPTS_SPEC="\ git deli head [-b=<branch>] [-m=<msg>] [<commit>] [-s] -git deli go [-c] [-s] [-b=<branch>] [-m=<msg>] <commit> +git deli eat [-c] [-s] [-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> +git deli push [-f] [<repository>] [<refspec>] +git deli pull [<repository>] [<ref>] -- common options: h,help! show the help m,message!= specify the commit message - 'head' and 'go' options: + 'head' and 'eat' actions: b,branch!= start a new branch instead of using the target one s,set-boundary use the <commit> as a boundary for the new head c,create-head start a new head at <commit> and delinearize into it @@ -110,7 +110,7 @@ main() { shift # drop the command, we already got it case "$arg_command" in head) cmd_head "$arg_msg" "$arg_branch" "$arg_set_boundary" "$@" ;; - go) cmd_go "$arg_msg" "$arg_branch" "$@" ;; + eat) cmd_eat "$arg_msg" "$arg_branch" "$@" ;; commit) cmd_commit "$arg_msg" "$@" ;; boundary) cmd_boundary "$arg_delete" "$@" ;; merge) cmd_merge "$@" ;; @@ -156,14 +156,16 @@ cmd_head () { git reset --soft "$new_head" || die "git-reset failed to set the new HEAD" } -cmd_go () { +cmd_eat () { # we're squashing whatever has been done atop of a given head view + false } cmd_commit () { msg="$1" shift # TODO run a git commit with all extra args, THEN run head with the original commit + false } cmd_boundary () { @@ -173,6 +175,7 @@ cmd_boundary () { commit="$1" # TODO this adds or removes the "extra parents" to the head commit + false } cmd_merge () { @@ -279,6 +282,7 @@ stream_independent_commit_set () { } # usage: revision <diff >shas +# TODO: Binary files (match ^Binary in output, ask for all lines) deli_diff_to_source_lines () { awk -f ' BEGIN { @@ -300,10 +304,40 @@ match($0, /^@@ -([0-9]+),([0-9]+) /, matched) && file!="" { END { if(ranges!="") print(ranges, "--", file); } - ' | xargs -l git annotate -l "$1" | cut -d'\t' -f1 + ' \ + | xargs -l git annotate -l "$1" \ + | cut -d'\t' -f1 } -deli_commit () { +deli_eat_commit () { + delihead="$1" + target="$2" + # TODO insert bounds + parents=$( git diff ${DELI_DIFF_ARGS:-} -p "$delihead" "$target" | deli_diff_to_source_lines | sort | uniq | stream_independent_commit_set ) + # check if the parents are ok + for i in parents + do git merge-base --is-ancestor "$delihead" "$i" || die "changes in $target are based on $delihead" + done + set -- $parents + echo "sourcing commit $1..." >&2 + merged_tree="$1^{tree}" + shift + while test $# -gt 0 + do + echo "merging commit $1..." >&2 + merged_tree=$( git merge-tree --write-tree "$merged_tree" "$1^{tree}" || die "merging of the parents failed" ) + shift + done + + new_commit=$( + ( echo "tree $merged_tree" + for i in $parents + do echo "parent $i" + done + git cat-file commit "$target" | sed '/^$/bx; /^tree /d ; /^parent /d ; n ; :x' + ) | git hash-object -t commit --stdin -w || die "could not save new commit for tree $merged_tree" + ) + false } @@ -317,6 +351,7 @@ deli_commit () { # Given the heads merge cleanly, the algorithm actually reduces to a very # simple task: # +# - we do a normal merge (this must solve cleanly) # - all parents from the original 2 commits are combined and reduced to # independent set # - all bounds from the original 2 commits are also reduced to an independent @@ -326,6 +361,8 @@ deli_commit () { # (TODO does it make sense to check this?) # - we write out the new commit with all parents and all bounds # +# TODO tbh this should be the very same as doing the `eat` operation after a +# merge, but the eat would need to handle multiple parents correctly. deli_merge () { false @@ -102,8 +102,8 @@ indepCommits = go [] $ "git-merge-base failed on " ++ show (acc, take batch xs) go (lines out) (drop batch xs) -diffToSources :: String -> String -> IO [String] -diffToSources base commit = do +diffToSources :: String -> String -> String -> IO [String] +diffToSources base commit bottom = do git <- gitProg (out, st) <- withCreateProcess @@ -115,7 +115,7 @@ diffToSources base commit = do bis <- fmap (uniq . sort . uniq . concat) . traverse (blameItem base) . sourceItems $ lines out - indepCommits bis + indepCommits (bottom:bis) makeEmptyTree :: IO String makeEmptyTree = do @@ -172,7 +172,7 @@ mergedCommitsTree (c:cs) = go c c cs _ -> fail "merge base output?" (out, st) <- withCreateProcess - (proc git ["merge-tree", "--write-tree", "--merge-base=" ++ b', t, c1]) + (proc git ["merge-tree", "--write-tree", "-Xno-renames", "--merge-base=" ++ b', t, c1]) {std_in = NoStream, std_out = CreatePipe} $ \_ (Just oh) _ p -> (,) <$> hGetContents' oh <*> waitForProcess p unless (st == ExitSuccess) . fail @@ -183,9 +183,9 @@ mergedCommitsTree (c:cs) = go c c cs _ -> fail "merge-tree output??" go b' t' cs -delinearize :: String -> String -> IO String -delinearize head commit = do - parents <- diffToSources head commit +delinearize :: String -> String -> String -> IO String +delinearize head commit bottom = do + parents <- diffToSources head commit bottom t <- mergedCommitsTree parents git <- gitProg -- create a new tree which is essentially commit-head+mergedtree @@ -193,10 +193,10 @@ delinearize head commit = do withCreateProcess (proc git - ["merge-tree", "--write-tree", "--merge-base=" ++ head, t, commit]) + ["merge-tree", "--write-tree", "-Xno-renames", "--merge-base=" ++ head, t, commit]) {std_in = NoStream, std_out = CreatePipe} $ \_ (Just oh) _ p -> (,) <$> hGetContents' oh <*> waitForProcess p - unless (st == ExitSuccess) . fail $ "final git-merge-tree failed" + unless (st == ExitSuccess) . fail $ "final git-merge-tree failed" ++ show (head,t,commit) t' <- case lines out of [x] -> pure x @@ -210,24 +210,50 @@ delinearize head commit = do unless (st == ExitSuccess) . fail $ "git-cat-file failed" let (msgHead, msgBody) = break null $ lines commitMsg' commitMsg = - unlines - $ ["tree " ++ t'] - ++ map ("parent " ++) parents - ++ filter ((/= ["parent"]) . take 1 . words) msgHead - ++ msgBody + unlines $ concat + [["tree " ++ t'], + map ("parent " ++) parents + , filter (not . flip elem [["parent"], ["tree"]] . take 1 . words) msgHead + , msgBody ] -- label the stuff with a proper commit (out, st) <- withCreateProcess (proc git ["hash-object", "-w", "-t", "commit", "--stdin"]) {std_in = CreatePipe, std_out = CreatePipe} $ \(Just ih) (Just oh) _ p -> - (,) <$> (hPutStr ih commitMsg >> hGetContents' oh) <*> waitForProcess p + (,) <$> (hPutStr ih commitMsg >> hClose ih >> hGetContents' oh) <*> waitForProcess p unless (st == ExitSuccess) . fail $ "git-hash-object final commit failed" commit' <- case lines out of [x] -> pure x _ -> fail "final has-object returned what?" - -- move the ref! - pure commit' + -- take the old head + (headMsg',st) <- + withCreateProcess + (proc git ["cat-file", "commit", head]) + {std_in = CreatePipe, std_out = CreatePipe} $ \(Just ih) (Just oh) _ p -> + (,) <$> (hPutStr ih commitMsg >> hClose ih >> hGetContents' oh) <*> waitForProcess p + unless (st == ExitSuccess) . fail $ "git-cat-file old head failed" + -- make a new one + let (hHead, hBody) = break null $ lines headMsg' + getP ["parent",x] = [x] + getP _ = [] + hParents <- indepCommits . (commit':) $ concatMap (getP.words) hHead + let headMsg = unlines $ concat + [ filter ((==["tree"]) . take 1 . words) msgHead + , map ("parent "++) hParents + , filter (not . flip elem [["parent"], ["tree"]] . take 1 . words) hHead + , hBody ] + -- write the new head + (out, st) <- + withCreateProcess + (proc git ["hash-object", "-w", "-t", "commit", "--stdin"]) + {std_in = CreatePipe, std_out = CreatePipe} $ \(Just ih) (Just oh) _ p -> + (,) <$> (hPutStr ih headMsg >> hClose ih >> hGetContents' oh) <*> waitForProcess p + unless (st == ExitSuccess) . fail $ "git-hash-object new head commit failed" + hcommit' <- + case lines out of + [x] -> pure x + _ -> fail "hash-object new head returned what?" + pure hcommit' -main = do - diffToSources "HEAD^" "HEAD" +main = undefined |
