Here is a Go program:
import "bufio"
import "log"
import "os/exec"
func check(e error) {
if e != nil {
log.Fatal(e)
}
}
func popen(name string, arg ...string) (*bufio.Scanner, error) {
cmd := exec.Command(name, arg...)
pipe, e := cmd.StdoutPipe()
if e != nil {
return nil, e
}
return bufio.NewScanner(pipe), cmd.Start()
}
import "os"
import "strconv"
import "time"
func main() {
gitLs, e := popen("git", "ls-files")
check(e)
files := map[string]bool{}
for gitLs.Scan() {
files[gitLs.Text()] = true
}
gitLog, e := popen(
"git", "log", "-m",
"--name-only", "--relative", "--pretty=format:%ct", ".",
)
check(e)
for len(files) > 0 {
gitLog.Scan()
sec, e := strconv.ParseInt(gitLog.Text(), 10, 64)
check(e)
unix := time.Unix(sec, 0)
for gitLog.Scan() {
name := gitLog.Text()
if name == "" {
break
}
if ! files[name] {
continue
}
os.Chtimes(name, unix, unix)
delete(files, name)
}
}
}
It is similar to this answer. It builds up a file list like that answer, but it builds from git ls-files instead of just looking in the working directory. This solves the problem of excluding .git, and it also solves the problem of untracked files. Also, that answer fails if the last commit of a file was a merge commit, which I solved with git log -m. Like the other answer, will stop once all files are found, so it doesn't have to read all the commits.
For example with git/git, as of this posting it only had to read
182 commits. Also it ignores old files from the history as needed, and it won't touch a file that has already been touched. Finally, it is faster than the other solution. Results with git/git repo:
PS C:\git> Measure-Command { ..\git-touch }
Milliseconds : 470