├── .gitignore ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin └── git-set-mtime ├── git-set-mtime.gemspec ├── lib └── git │ └── set │ ├── mtime.rb │ └── mtime │ └── version.rb └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.so 12 | *.o 13 | *.a 14 | mkmf.log 15 | /git-set-mtime 16 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in git-set-mtime.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Sho Kusano 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # git-set-mtime 2 | 3 | [![Build Status](https://drone.io/github.com/rosylilly/git-set-mtime/status.png)](https://drone.io/github.com/rosylilly/git-set-mtime/latest) 4 | 5 | set files mtime by latest commit time. 6 | 7 | for Dockerfile building on CI servers(docker checking mtime for build cache) 8 | 9 | ## Installation 10 | 11 | Install by rubygems: 12 | 13 | $ gem install git-set-mtime 14 | 15 | Install by golang: 16 | $ go get github.com/rosylilly/git-set-mtime 17 | 18 | Install from binary: 19 | 20 | You can download pre-build binaries from [drone.io](https://drone.io/github.com/rosylilly/git-set-mtime/files)(Windows, Mac and Linux). 21 | 22 | ## Usage 23 | 24 | ```shell 25 | $ git set-mtime 26 | ``` 27 | 28 | ## Contributing 29 | 30 | 1. Fork it ( https://github.com/rosylilly/git-set-mtime/fork ) 31 | 2. Create your feature branch (`git checkout -b my-new-feature`) 32 | 3. Commit your changes (`git commit -am 'Add some feature'`) 33 | 4. Push to the branch (`git push origin my-new-feature`) 34 | 5. Create a new Pull Request 35 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | -------------------------------------------------------------------------------- /bin/git-set-mtime: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'git/set/mtime' 4 | Git::Set::Mtime::CLI.start(ARGV) 5 | -------------------------------------------------------------------------------- /git-set-mtime.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'git/set/mtime/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "git-set-mtime" 8 | spec.version = Git::Set::Mtime::VERSION 9 | spec.authors = ["Sho Kusano"] 10 | spec.email = ["sho-kusano@mgnt-inc.jp"] 11 | spec.summary = %q{set files mtime by latest commit time} 12 | spec.description = %q{set files mtime by latest commit time} 13 | spec.homepage = "https://github.com/rosylilly/git-set-mtime" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files -z`.split("\x0") 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_development_dependency "bundler", "~> 1.7" 22 | spec.add_development_dependency "rake", "~> 10.0" 23 | spec.add_dependency 'thor', '~> 0.19.1' 24 | end 25 | -------------------------------------------------------------------------------- /lib/git/set/mtime.rb: -------------------------------------------------------------------------------- 1 | require 'thor' 2 | require 'time' 3 | require 'git/set/mtime/version' 4 | require 'open3' 5 | 6 | module Git::Set::Mtime 7 | class CLI < Thor 8 | GIT_LOG_ARGS = %w[git log -1 --pretty=format:%ad --date local].freeze 9 | 10 | desc 'apply', 'apply mtime to files' 11 | def apply 12 | files = `git ls-files` 13 | files.each_line do |file| 14 | file.chomp! 15 | mtime_str, status = Open3.capture2e(*GIT_LOG_ARGS, file) 16 | raise mtime_str unless status.success? 17 | mtime = Time.parse(mtime_str) 18 | File.utime(File.atime(file), mtime, file) 19 | puts "#{mtime} #{file}" 20 | end 21 | end 22 | 23 | default_task :apply 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/git/set/mtime/version.rb: -------------------------------------------------------------------------------- 1 | module Git 2 | module Set 3 | module Mtime 4 | VERSION = "0.0.2" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "path/filepath" 8 | "strings" 9 | "time" 10 | 11 | "golang.org/x/sys/unix" 12 | ) 13 | 14 | const rfc2822 = "Mon, 2 Jan 2006 15:04:05 -0700" 15 | 16 | // Set the (a|m)time on `path` without following symlinks 17 | func lutimes(path string, atime, mtime time.Time) error { 18 | times := []unix.Timespec{ 19 | unix.NsecToTimespec(atime.UnixNano()), 20 | unix.NsecToTimespec(mtime.UnixNano()), 21 | } 22 | return unix.UtimesNanoAt(unix.AT_FDCWD, path, times, unix.AT_SYMLINK_NOFOLLOW) 23 | } 24 | 25 | func main() { 26 | lsFiles := exec.Command("git", "ls-files", "-z") 27 | 28 | out, err := lsFiles.Output() 29 | if err != nil { 30 | fmt.Fprint(os.Stderr, err) 31 | os.Exit(1) 32 | } 33 | 34 | dirMTimes := map[string]time.Time{} 35 | 36 | files := strings.Split(strings.TrimRight(string(out), "\x00"), "\x00") 37 | for _, file := range files { 38 | gitLog := exec.Command("git", "log", "-1", "--date=rfc2822", "--format=%cd", file) 39 | 40 | out, err := gitLog.Output() 41 | 42 | if err != nil { 43 | fmt.Fprint(os.Stderr, err) 44 | os.Exit(1) 45 | } 46 | 47 | mStr := strings.TrimSpace(strings.TrimLeft(string(out), "Date:")) 48 | mTime, err := time.Parse(rfc2822, mStr) 49 | 50 | if err != nil { 51 | fmt.Fprintf(os.Stderr, "%s on %s", err, file) 52 | os.Exit(1) 53 | } 54 | 55 | // Loop over each directory in the path to `file`, updating `dirMTimes` 56 | // to take the most recent time seen. 57 | dir := filepath.Dir(file) 58 | for { 59 | if other, ok := dirMTimes[dir]; ok { 60 | if mTime.After(other) { 61 | // file mTime is more recent than previous seen for 'dir' 62 | dirMTimes[dir] = mTime 63 | } 64 | } else { 65 | // first occurrence of dir 66 | dirMTimes[dir] = mTime 67 | } 68 | 69 | // Remove one directory from the path until it isn't changed anymore 70 | if dir == filepath.Dir(dir) { 71 | break 72 | } 73 | dir = filepath.Dir(dir) 74 | } 75 | 76 | err = lutimes(file, mTime, mTime) 77 | if err != nil { 78 | fmt.Fprintf(os.Stderr, "%s on %s", err, file) 79 | os.Exit(1) 80 | } 81 | 82 | fmt.Printf("%s: %s\n", file, mTime) 83 | } 84 | 85 | for dir, mTime := range dirMTimes { 86 | err = lutimes(dir, mTime, mTime) 87 | if err != nil { 88 | fmt.Fprintf(os.Stderr, "%s on %s", err, dir) 89 | os.Exit(1) 90 | } 91 | fmt.Printf("%s: %s\n", dir, mTime) 92 | } 93 | } 94 | --------------------------------------------------------------------------------