Eu escrevi a seguinte implementação na linguagem TXR . No início, usei seu algoritmo para determinar a contagem movida. No entanto, notei que ele produz resultados que não são úteis: por exemplo, identificava valores positivos para "linhas movidas" em alterações que continham nada além de +
linhas, simplesmente porque algumas das linhas +
eram duplicadas uma da outra. . O novo algoritmo é discutido no Notes no final.
O programa completo:
#!/usr/bin/env txr
@(bind option-spec
@(list (opt nil "since" :str
"Specifies the starting date (passed \
\ through to git); it is mandatory.")
(opt nil "help" :bool
"Prints this help text")))
@(bind parsed-opts @(getopts option-spec *args*))
@(if (or [parsed-opts "help"] (not [parsed-opts "since"])))
@ (output)
usage: @{self-path} --since=<date> -- git arguments
@ (end)
@ (do (opthelp option-spec)
(exit 0))
@(end)
@(do
(defun histogram (strings)
[group-reduce (hash :equal-based) identity (op succ @1) strings 0])
(defun moved (a b)
(let* ((hist-a (histogram a))
(hist-b (histogram b))
(isec [hash-isec hist-a hist-b min]))
[reduce-left + (hash-values isec) 0])))
@(next (open-command 'git log --since=@[parsed-opts "since"] \
\ --date=short --pretty=format:"%H:%ad%x09" \
\ --numstat @{parsed-opts.out-args}'))
@(repeat)
@sha:@date@\t
@ (collect :gap 0)
@added@\t@removed@\t@rawpath
@ (next :string rawpath)
@ (cases)
@pro/{@before => @after}/@epi
@ (bind path '@pro/@after/@epi')
@ (or)
@before => @after
@ (bind path after)
@ (or)
@ (bind path rawpath)
@ (end)
@ (next (open-command 'git show -p @sha -- @path'))
@ (collect :vars ((+line nil) (-line nil)))
@ (cases)
+@{+line}
@ (or)
-@{-line}
@ (end)
@ (end)
@ (flatten -line +line)
@ (bind moved @(moved +line -line))
@ (end)
@ (output)
@date@\t
@ (repeat)
@added@\t@removed@\t@moved@\t@rawpath
@ (end)
@ (end)
@(end)
Eu tenho isso em um arquivo executável marcado chamado movedlines.txr
e o uso de exemplo é:
$ ./movedlines.txr --since=2017-01-01 path/to
A opção --since
é obrigatória; o path/to
é um argumento opcional passado para git
. Se você não especificar a opção obrigatória ou especificar --help
, o programa imprimirá um resumo de ajuda e será encerrado.
Notas:
-
Alterei ligeiramente o formato de saída do seu comando
git
do exemplo para incluir o SHA à esquerda da data, separado por dois pontos: consulte o%H
. O programa analisa isso e, em seguida, ele pode usar o SHA para fazer umgit show -p
em cada arquivo em cada conjunto. O SHA é omitido quando a imitação da saída é regurgitada pelo programa com a coluna movida adicional. -
Existe uma complicação em que a saída do git mostra renomeações. A sintaxe se enquadra em três casos, que são analisados de forma clara e fácil por casos usando a construção
@(cases)
. Se um caminho inteiro for renomeado, a renomeação seráfrom => to
. Se apenas alguns componentes forem renomeados, serábe/fore/{from => to}/after
. Eu não sei se a sintaxe múltipla de chaves ocorre em um caminho; Eu não vi isso. Precisamos usar essa sintaxe e convertê-la em um caminho simples, porque o git não entende. Ou seja devemos converterbe/fore/{from => to}/after
embe/fore/to/after
. Talvez haja uma opção git para que os caminhos sejam exibidos dessa forma sem a notação; Eu não me incomodei em procurar. -
O script não é robusto contra espaços em nomes de arquivos devido ao uso de
open-command
. Para isso, precisamos deopen-process
, o que leva uma lista de argumentos. -
O algoritmo calcula histogramas de frequência separados do diff
-
e+
linhas (menos o primeiro-
ou+
). Em seguida, calcula a interseção desses conjuntos, que mantém apenas as entradas do histograma que ocorrem nos dois histogramas. A função de junção para a interseção émin
. Por exemplo, suponha que a linhaabc
tenha sido adicionada 5 vezes e removida 3 vezes.(min 3 5)
é3
e esse é o número deabc
linhas movidas. Isso comuta. Se 3 ocorrências deabc
forem removidas e 5 adicionadas, isso significa 3 movimentos. Isso não é de forma alguma considerado como um algoritmo perfeito para detectar movimentos. Ele tem armadilhas óbvias, como não detectar linhas que, de fato, se movem, mas também passam por uma mudança trivial de espaço em branco, como recuo.